diff --git a/.editorconfig b/.editorconfig index 7400f6df98b17..7ff7c2c3d8ba9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -61,6 +61,9 @@ dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion +# Whitespace options +dotnet_style_allow_multiple_blank_lines_experimental = false + # Non-private static fields are PascalCase dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields @@ -182,6 +185,11 @@ csharp_indent_case_contents_when_block = true csharp_indent_switch_labels = true csharp_indent_labels = flush_left +# Whitespace options +csharp_style_allow_embedded_statements_on_same_line_experimental = false +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false + # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion @@ -233,6 +241,10 @@ csharp_prefer_braces = true:silent csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true +# Currently only enabled for C# due to crash in VB analyzer. VB can be enabled once +# https://github.com/dotnet/roslyn/pull/54259 has been published. +dotnet_style_allow_statement_immediately_after_block_experimental = false + [src/CodeStyle/**.{cs,vb}] # warning RS0005: Do not use generic CodeAction.Create to create CodeAction dotnet_diagnostic.RS0005.severity = none @@ -272,6 +284,21 @@ csharp_style_var_for_built_in_types = true:warning csharp_style_var_when_type_is_apparent = true:warning csharp_style_var_elsewhere = true:warning +# dotnet_style_allow_multiple_blank_lines_experimental +dotnet_diagnostic.IDE2000.severity = warning + +# csharp_style_allow_embedded_statements_on_same_line_experimental +dotnet_diagnostic.IDE2001.severity = warning + +# csharp_style_allow_blank_lines_between_consecutive_braces_experimental +dotnet_diagnostic.IDE2002.severity = warning + +# dotnet_style_allow_statement_immediately_after_block_experimental +dotnet_diagnostic.IDE2003.severity = warning + +# csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental +dotnet_diagnostic.IDE2004.severity = warning + [src/{VisualStudio}/**/*.{cs,vb}] # CA1822: Make member static # Not enforced as a build 'warning' for 'VisualStudio' layer due to large number of false positives from https://github.com/dotnet/roslyn-analyzers/issues/3857 and https://github.com/dotnet/roslyn-analyzers/issues/3858 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..efdda18ee9aca --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Dotnet-format -w Roslyn.sln +abce41d282ac631be5217140f1bd46d0e250ad02 diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index f31edeb4d3bd8..f16df6c5ce694 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -2,14 +2,25 @@ resources: - repo: self clean: true -# Variables defined in yml cannot be overridden at queue time instead overrideable variables must be defined in the web gui. -# Commenting out until AzDO supports something like the runtime parameters outlined here: https://github.com/Microsoft/azure-pipelines-yaml/pull/129 -#variables: -# SignType: real -# SkipTests: false -# SkipApplyOptimizationData: false -# IbcDrop: 'default' -# PRNumber: 'default' +parameters: +- name: IbcDrop + type: string + default: default + +- name: SignType + default: real + type: string + values: + - real + - test + +- name: SkipApplyOptimizationData + type: boolean + default: false + +- name: SkipTests + type: boolean + default: true # The variables `_DotNetArtifactsCategory` and `_DotNetValidationArtifactsCategory` are required for proper publishing of build artifacts. See https://github.com/dotnet/roslyn/pull/38259 variables: @@ -20,46 +31,36 @@ variables: - group: DotNet-Roslyn-SDLValidation-Params # To retrieve OptProf data we need to authenticate to the VS drop storage. - # If the pipeline is running in DevDiv, the account has access to the VS drop storage. - # Get $AccessToken-dotnet-build-bot-public-repo from DotNet-GitHub-Versions-Repo-Write - - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - - group: DotNet-GitHub-Versions-Repo-Write - - name: _DevDivDropAccessToken - value: $(System.AccessToken) - # If the pipeline is running in dnceng: # Get access token with $dn-bot-devdiv-drop-rw-code-rw and dn-bot-dnceng-build-rw-code-rw from DotNet-VSTS-Infra-Access # Get $dotnetfeed-storage-access-key-1 from DotNet-Blob-Feed # Get $microsoft-symbol-server-pat and $symweb-symbol-server-pat from DotNet-Symbol-Server-Pats # Get $AccessToken-dotnet-build-bot-public-repo from DotNet-Versions-Publish - - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - group: DotNet-Blob-Feed - - group: DotNet-Symbol-Server-Pats - - group: DotNet-Versions-Publish - - group: DotNet-VSTS-Infra-Access - - group: DotNet-DevDiv-Insertion-Workflow-Variables - - name: _DevDivDropAccessToken - value: $(dn-bot-devdiv-drop-rw-code-rw) + - group: DotNet-Blob-Feed + - group: DotNet-Symbol-Server-Pats + - group: DotNet-Versions-Publish + - group: DotNet-VSTS-Infra-Access + - group: DotNet-DevDiv-Insertion-Workflow-Variables + - name: _DevDivDropAccessToken + value: $(dn-bot-devdiv-drop-rw-code-rw) + - group: ManagedLanguageSecrets + + - name: BuildConfiguration + value: release + - name: Roslyn.GitHubEmail + value: dotnet-build-bot@microsoft.com + - name: Roslyn.GitHubToken + value: $(AccessToken-dotnet-build-bot-public-repo) + - name: Roslyn.GitHubUserName + value: dotnet-build-bot + + - name: Insertion.InsertToolset + value: true + - name: Insertion.CreateDraftPR + value: true + - name: Insertion.TitlePrefix + value: '[Auto Insertion]' stages: -- stage: setup - displayName: Build Setup - - jobs: - - job: SetBuildNumber - pool: - vmImage: vs2017-win2016 - steps: - # Make sure our two pipelines generate builds with distinct build numbers to avoid confliction. - # i.e. DevDiv builds will have even rev# whereas dnceng builds will be odd. - - task: PowerShell@2 - displayName: Update BuildNumber - inputs: - filePath: 'eng\update-build-number.ps1' - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - arguments: '-buildNumber $(Build.BuildNumber) -oddOrEven even' - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - arguments: '-buildNumber $(Build.BuildNumber) -oddOrEven odd' - condition: eq(variables['System.JobAttempt'], '1') - stage: build displayName: Build and Test @@ -76,17 +77,9 @@ stages: - job: OfficialBuild displayName: Official Build timeoutInMinutes: 360 - # Conditionally set build pool so we can share this YAML when building with different pipeline (devdiv vs dnceng) pool: - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - name: VSEngSS-MicroBuild2017 - demands: - - msbuild - - visualstudio - - DotNetFramework - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - name: NetCoreInternal-Pool - queue: BuildPool.Server.Amd64.VS2017 + name: NetCoreInternal-Pool + queue: BuildPool.Server.Amd64.VS2017 steps: - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" @@ -98,23 +91,7 @@ stages: inputs: type: 'Build' tags: 'OfficialBuild' - condition: and(succeeded(), endsWith(variables['SourceBranchName'], '-vs-deps'), eq(variables['PRNumber'], 'default')) - - - task: tagBuildOrRelease@0 - displayName: Tag PR validation build - inputs: - type: 'Build' - tags: | - PRValidationBuild - PRNumber:$(PRNumber) - condition: and(succeeded(), ne(variables['PRNumber'], 'default')) - - - task: PowerShell@2 - displayName: Setup branch for insertion validation - inputs: - filePath: 'eng\setup-pr-validation.ps1' - arguments: '-sourceBranchName $(SourceBranchName) -prNumber $(PRNumber)' - condition: and(succeeded(), ne(variables['PRNumber'], 'default')) + condition: and(succeeded(), endsWith(variables['SourceBranchName'], '-vs-deps')) - task: tagBuildOrRelease@0 displayName: Tag main validation build @@ -128,8 +105,7 @@ stages: displayName: Merge main-vs-deps into source branch inputs: filePath: 'scripts\merge-vs-deps.ps1' - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - arguments: '-accessToken $(dn-bot-dnceng-build-rw-code-rw)' + arguments: '-accessToken $(dn-bot-dnceng-build-rw-code-rw)' condition: and(succeeded(), eq(variables['SourceBranchName'], 'main')) - powershell: Write-Host "##vso[task.setvariable variable=VisualStudio.DropName]Products/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)" @@ -142,20 +118,15 @@ stages: # Authenticate with service connections to be able to publish packages to external nuget feeds. - task: NuGetAuthenticate@0 inputs: - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk, devdiv/engineering + nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk, devdiv/engineering - # Needed because the dnceng image we build on fails the next task without it + # Needed because the build fails the NuGet Tools restore without it - task: UseDotNet@2 displayName: 'Use .NET Core sdk' inputs: packageType: sdk useGlobalJson: true workingDirectory: '$(Build.SourcesDirectory)' - installationPath: '$(Build.SourcesDirectory)/.dotnet' - condition: eq(variables['System.TeamProject'], 'internal') # Needed to restore the Microsoft.DevDiv.Optimization.Data.PowerShell package - task: NuGetCommand@2 @@ -171,9 +142,7 @@ stages: inputs: signType: $(SignType) zipSources: false - # If running in dnceng, we need to use a feed different from default - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json condition: and(succeeded(), in(variables['SignType'], 'test', 'real')) - task: PowerShell@2 @@ -212,7 +181,7 @@ stages: inputs: filePath: 'eng\publish-assets.ps1' arguments: '-configuration $(BuildConfiguration) -branchName "$(SourceBranchName)"' - condition: and(succeeded(), eq(variables['PRNumber'], 'default')) + condition: succeeded() # Publish OptProf configuration files # The env variable is required to enable cross account access using PAT (dnceng -> devdiv) @@ -282,12 +251,8 @@ stages: command: push packagesToPush: '$(Build.SourcesDirectory)\artifacts\VSSetup\$(BuildConfiguration)\DevDivPackages\**\*.nupkg' allowPackageConflicts: true - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - feedsToUse: config - publishVstsFeed: '97a41293-2972-4f48-8c0e-05493ae82010' - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - nuGetFeedType: external - publishFeedCredentials: 'DevDiv - VS package feed' + nuGetFeedType: external + publishFeedCredentials: 'DevDiv - VS package feed' condition: succeeded() # Publish an artifact that the RoslynInsertionTool is able to find by its name. @@ -325,25 +290,89 @@ stages: publishUsingPipelines: true dependsOn: - OfficialBuild - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - queue: - name: Hosted VS2017 - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - pool: - vmImage: vs2017-win2016 - -# We need to skip post-build stages for PR validation build, but it can only be identified by -# the runtime variable 'PRNumber', thus this dummy stage. Also the dummy job is required -# otherwise AzDO would just repeat jobs from previous stage. -- stage: SetValidateDependency - displayName: Setup the dependency for post-build stages - condition: and(succeeded(), eq(variables['PRNumber'], 'default')) + pool: + vmImage: vs2017-win2016 + +- stage: insert + dependsOn: + - build + - publish_using_darc + displayName: Insert to VS + jobs: - - job: Log - displayName: Log + - job: insert + displayName: Insert to VS + pool: + vmImage: windows-2019 steps: - - powershell: Write-Host "Setup the dependency for post-build stages." - displayName: Log + - checkout: none + + - task: NuGetCommand@2 + displayName: 'Install RIT from Azure Artifacts' + inputs: + command: custom + arguments: 'install RoslynTools.VisualStudioInsertionTool -PreRelease -Source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + + - powershell: | + $response = Invoke-RestMethod -Headers @{Authorization = "Bearer $(System.AccessToken)"} "https://dev.azure.com/dnceng/internal/_apis/git/repositories/dotnet-roslyn/items?path=eng/config/PublishData.json&api-version=6.0" + $branchName = "$(Build.SourceBranch)".Substring("refs/heads/".Length) + $branchData = $response.branches.$branchName + if (!$branchData) + { + Write-Host "No PublishData found for branch '$branchName'. Using PublishData for branch 'main'." + $branchData = $response.branches.main + } + if ($null -ne $branchData.insertionCreateDraftPR) + { + Write-Host "##vso[task.setvariable variable=Insertion.CreateDraftPR]$($branchData.insertionCreateDraftPR)" + } + if ($null -ne $branchData.insertionTitlePrefix) + { + Write-Host "##vso[task.setvariable variable=Insertion.TitlePrefix]$($branchData.insertionTitlePrefix)" + } + if ($null -ne $branchData.insertToolset) + { + Write-Host "##vso[task.setvariable variable=Insertion.InsertToolset]$($branchData.insertToolset)" + } + + Write-Host "##vso[task.setvariable variable=Insertion.AutoComplete]$(-not $branchData.insertionCreateDraftPR)" + Write-Host "##vso[task.setvariable variable=ComponentBranchName]$branchName" + Write-Host "##vso[task.setvariable variable=VSBranchName]$($branchData.vsBranch)" + displayName: Set Insertion Variables + + - powershell: | + mv RoslynTools.VisualStudioInsertionTool.* RIT + .\RIT\tools\OneOffInsertion.ps1 ` + -autoComplete "$(Insertion.AutoComplete)" ` + -buildQueueName "$(Build.DefinitionName)" ` + -cherryPick "(default)" ` + -clientId "$(ClientId)" ` + -clientSecret "$(ClientSecret)" ` + -componentAzdoUri "$(System.CollectionUri)" ` + -componentBranchName "$(ComponentBranchName)" ` + -componentGitHubRepoName "dotnet/roslyn" ` + -componentName "Roslyn" ` + -componentProjectName "internal" ` + -createDraftPR "$(Insertion.CreateDraftPR)" ` + -defaultValueSentinel "(default)" ` + -dropPath "(default)" ` + -insertCore "(default)" ` + -insertDevDiv "(default)" ` + -insertionCount "1" ` + -insertToolset "$(Insertion.InsertToolset)" ` + -titlePrefix "$(Insertion.TitlePrefix)" ` + -queueValidation "true" ` + -requiredValueSentinel "REQUIRED" ` + -reviewerGUID "6c25b447-1d90-4840-8fde-d8b22cb8733e" ` + -specificBuild "$(Build.BuildNumber)" ` + -updateAssemblyVersions "(default)" ` + -updateCoreXTLibraries "(default)" ` + -visualStudioBranchName "$(VSBranchName)" ` + -writePullRequest "prid.txt" ` + displayName: 'Run OneOffInsertion.ps1' + + - script: 'echo. && echo. && type "prid.txt" && echo. && echo.' + displayName: 'Report PR URL' - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: eng\common\templates\post-build\post-build.yml @@ -353,12 +382,6 @@ stages: # https://github.com/dotnet/arcade/issues/2871 is resolved. enableSymbolValidation: false enableSourceLinkValidation: false - # It's important that post-build stages are depends on 'SetValidateDependency' stage instead of 'build', - # since we don't want to publish validation build. - validateDependsOn: - - SetValidateDependency - dependsOn: - - SetValidateDependency # Enable SDL validation, passing through values from the 'DotNet-Roslyn-SDLValidation-Params' group. SDLValidationParameters: enable: true diff --git a/azure-pipelines-pr-validation.yml b/azure-pipelines-pr-validation.yml new file mode 100644 index 0000000000000..7f1078fdc5928 --- /dev/null +++ b/azure-pipelines-pr-validation.yml @@ -0,0 +1,196 @@ +resources: +- repo: self + clean: true + +# Variables defined in yml cannot be overridden at queue time instead overrideable variables must be defined in the web gui. +# Commenting out until AzDO supports something like the runtime parameters outlined here: https://github.com/Microsoft/azure-pipelines-yaml/pull/129 +#variables: +# SignType: test +# IbcDrop: 'default' + +parameters: +- name: PRNumber + type: number +- name: CommitSHA + type: string + +# The variables `_DotNetArtifactsCategory` and `_DotNetValidationArtifactsCategory` are required for proper publishing of build artifacts. See https://github.com/dotnet/roslyn/pull/38259 +variables: + - name: _DotNetArtifactsCategory + value: .NETCore + - name: _DotNetValidationArtifactsCategory + value: .NETCoreValidation + - group: DotNet-Roslyn-SDLValidation-Params + + # To retrieve OptProf data we need to authenticate to the VS drop storage. + # If the pipeline is running in DevDiv, the account has access to the VS drop storage. + # Get $AccessToken-dotnet-build-bot-public-repo from DotNet-GitHub-Versions-Repo-Write + - group: DotNet-GitHub-Versions-Repo-Write + - name: _DevDivDropAccessToken + value: $(System.AccessToken) + +stages: +- stage: build + displayName: Build and Test + + jobs: + + - job: PRValidationBuild + displayName: PR Validation Build + timeoutInMinutes: 360 + # Conditionally set build pool so we can share this YAML when building with different pipeline + pool: + name: VSEngSS-MicroBuild2017 + demands: + - msbuild + - visualstudio + - DotNetFramework + + steps: + - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" + displayName: Setting SourceBranchName variable + condition: succeeded() + + - task: tagBuildOrRelease@0 + displayName: Tag PR validation build + inputs: + type: 'Build' + tags: | + PRValidationBuild + PRNumber:${{ parameters.PRNumber }} + CommitSHA:${{ parameters.CommitSHA }} + condition: succeeded() + + - task: PowerShell@2 + displayName: Setup branch for insertion validation + inputs: + filePath: 'eng\setup-pr-validation.ps1' + arguments: '-sourceBranchName $(SourceBranchName) -prNumber ${{ parameters.PRNumber }} -commitSHA ${{ parameters.CommitSHA }}' + condition: succeeded() + + - powershell: Write-Host "##vso[task.setvariable variable=VisualStudio.DropName]Products/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)" + displayName: Setting VisualStudio.DropName variable + + - task: NuGetToolInstaller@0 + inputs: + versionSpec: '4.9.2' + + # Authenticate with service connections to be able to publish packages to external nuget feeds. + - task: NuGetAuthenticate@0 + inputs: + nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk + + # Needed to restore the Microsoft.DevDiv.Optimization.Data.PowerShell package + - task: NuGetCommand@2 + displayName: Restore internal tools + inputs: + command: restore + feedsToUse: config + restoreSolution: 'eng\common\internal\Tools.csproj' + nugetConfigPath: 'NuGet.config' + restoreDirectory: '$(Build.SourcesDirectory)\.packages' + + - task: MicroBuildSigningPlugin@2 + inputs: + signType: $(SignType) + zipSources: false + condition: and(succeeded(), in(variables['SignType'], 'test', 'real')) + + - task: PowerShell@2 + displayName: Build + inputs: + filePath: eng/build.ps1 + arguments: -ci + -restore + -build + -pack + -sign + -publish + -binaryLog + -configuration $(BuildConfiguration) + -officialBuildId $(Build.BuildNumber) + -officialSkipTests $(SkipTests) + -officialSkipApplyOptimizationData $(SkipApplyOptimizationData) + -officialSourceBranchName $(SourceBranchName) + -officialIbcDrop $(IbcDrop) + -officialVisualStudioDropAccessToken $(_DevDivDropAccessToken) + /p:RepositoryName=$(Build.Repository.Name) + /p:VisualStudioDropName=$(VisualStudio.DropName) + /p:DotNetSignType=$(SignType) + /p:DotNetPublishToBlobFeed=false + /p:PublishToSymbolServer=false + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) + /p:DotnetPublishUsingPipelines=false + /p:PreReleaseVersionLabel=pr-validation + condition: succeeded() + + # Publish OptProf generated JSON files as a build artifact. This allows for easy inspection from + # a build execution. + - task: PublishBuildArtifacts@1 + displayName: Publish OptProf Data Files + inputs: + PathtoPublish: '$(Build.SourcesDirectory)\artifacts\OptProf\$(BuildConfiguration)\Data' + ArtifactName: 'OptProf Data Files' + condition: succeeded() + + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)' + ArtifactName: 'Build Diagnostic Files' + publishLocation: Container + continueOnError: true + condition: succeededOrFailed() + + - task: PublishBuildArtifacts@1 + displayName: Publish Ngen Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)\ngen' + ArtifactName: 'NGen Logs' + publishLocation: Container + continueOnError: true + condition: succeeded() + + - task: PublishTestResults@2 + displayName: Publish xUnit Test Results + inputs: + testRunner: XUnit + testResultsFiles: '$(Build.SourcesDirectory)\artifacts\TestResults\$(BuildConfiguration)\*.xml' + mergeTestResults: true + testRunTitle: 'Unit Tests' + condition: and(succeededOrFailed(), ne(variables['SkipTests'], 'true')) + + # Publishes setup VSIXes to a drop. + # Note: The insertion tool looks for the display name of this task in the logs. + - task: ms-vseng.MicroBuildTasks.4305a8de-ba66-4d8b-b2d1-0dc4ecbbf5e8.MicroBuildUploadVstsDropFolder@1 + displayName: Upload VSTS Drop + inputs: + DropName: $(VisualStudio.DropName) + DropFolder: 'artifacts\VSSetup\$(BuildConfiguration)\Insertion' + AccessToken: $(_DevDivDropAccessToken) + condition: succeeded() + + # Publish insertion packages to CoreXT store. + - task: NuGetCommand@2 + displayName: Publish CoreXT Packages + inputs: + command: push + packagesToPush: '$(Build.SourcesDirectory)\artifacts\VSSetup\$(BuildConfiguration)\DevDivPackages\**\*.nupkg' + allowPackageConflicts: true + feedsToUse: config + publishVstsFeed: '97a41293-2972-4f48-8c0e-05493ae82010' + condition: succeeded() + + # Publish an artifact that the RoslynInsertionTool is able to find by its name. + - task: PublishBuildArtifacts@1 + displayName: Publish Artifact VSSetup + inputs: + PathtoPublish: 'artifacts\VSSetup\$(BuildConfiguration)' + ArtifactName: 'VSSetup' + condition: succeeded() + + - task: ms-vseng.MicroBuildTasks.521a94ea-9e68-468a-8167-6dcf361ea776.MicroBuildCleanup@1 + displayName: Perform Cleanup Tasks + condition: succeededOrFailed() diff --git a/azure-pipelines-richnav.yml b/azure-pipelines-richnav.yml index 6652eb3757fae..d327060f6c65e 100644 --- a/azure-pipelines-richnav.yml +++ b/azure-pipelines-richnav.yml @@ -1,6 +1,20 @@ +# Branches that trigger a build on commit trigger: - main +- main-vs-deps +- release/* +- features/* +- demos/* + +# Branches that trigger builds on PR pr: none +# Temporarily disabling richnav job on PRs +# pr: +# - main +# - main-vs-deps +# - release/* +# - features/* +# - demos/* jobs: - job: RichCodeNav_Indexing diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 5a28acb0fe789..768a966e61f4e 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,27 +10,34 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | -| [Record structs](https://github.com/dotnet/csharplang/issues/4334) | [record-structs](https://github.com/dotnet/roslyn/tree/features/record-structs) | [In Progress](https://github.com/dotnet/roslyn/issues/51199) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | -| [Global Using Directive](https://github.com/dotnet/csharplang/issues/3428) | [GlobalUsingDirective](https://github.com/dotnet/roslyn/tree/features/GlobalUsingDirective) | [In Progress](https://github.com/dotnet/roslyn/issues/51307) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | [AlekseyTs](https://github.com/AlekseyTs) | | [Static Abstract Members In Interfaces](https://github.com/dotnet/csharplang/issues/4436) | [StaticAbstractMembersInInterfaces](https://github.com/dotnet/roslyn/tree/features/StaticAbstractMembersInInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/52221) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [File-scoped namespace](https://github.com/dotnet/csharplang/issues/137) | [FileScopedNamespaces](https://github.com/dotnet/roslyn/tree/features/FileScopedNamespaces) | [In Progress](https://github.com/dotnet/roslyn/issues/49000) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [chsienki](https://github.com/chsienki) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [Interpolated string improvements](https://github.com/dotnet/csharplang/issues/4487) | [interpolated-string](https://github.com/dotnet/roslyn/tree/features/interpolated-string) | [In Progress](https://github.com/dotnet/roslyn/issues/51499) | [333fred](https://github.com/333fred) | [AlekseyTs](https://github.com/AlekseyTs), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) | | [Parameterless struct constructors](https://github.com/dotnet/csharplang/issues/99) | [struct-ctors](https://github.com/dotnet/roslyn/tree/features/struct-ctors) | [In Progress](https://github.com/dotnet/roslyn/issues/51698) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [jcouv](https://github.com/jouv) | | [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/lambda-improvements.md) | [lambdas](https://github.com/dotnet/roslyn/tree/features/lambdas) | [In Progress](https://github.com/dotnet/roslyn/issues/52192) | [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) | | [nameof(parameter)](https://github.com/dotnet/csharplang/issues/373) | main | [In Progress](https://github.com/dotnet/roslyn/issues/40524) | [jcouv](https://github.com/jcouv) | TBD | [jcouv](https://github.com/jcouv) | -| [Improved Definite Assignment](https://github.com/dotnet/csharplang/issues/4465) | [improved-definite-assignment](https://github.com/dotnet/roslyn/tree/features/improved-definite-assignment) | [Merged into 17.0](https://github.com/dotnet/roslyn/issues/51463) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) | | [Relax ordering of `ref` and `partial` modifiers](https://github.com/dotnet/csharplang/issues/946) | [ref-partial](https://github.com/dotnet/roslyn/tree/features/ref-partial) | In Progress | [alrz](https://github.com/alrz) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | | [Parameter null-checking](https://github.com/dotnet/csharplang/issues/2145) | [param-nullchecking](https://github.com/dotnet/roslyn/tree/features/param-nullchecking) | [In Progress](https://github.com/dotnet/roslyn/issues/36024) | [fayrose](https://github.com/fayrose) | [agocke](https://github.com/agocke) | [jaredpar](https://github.com/jaredpar) | | [Caller expression attribute](https://github.com/dotnet/csharplang/issues/287) | [caller-argument-expression](https://github.com/dotnet/roslyn/tree/features/caller-argument-expression) | [In Progress](https://github.com/dotnet/roslyn/issues/52745) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred),[AlekseyTs](https://github.com/AlekseyTs) | [jcouv](https://github.com/jcouv) | | [Generic attributes](https://github.com/dotnet/csharplang/issues/124) | [generic-attributes](https://github.com/dotnet/roslyn/tree/features/generic-attributes) | [In Progress](https://github.com/dotnet/roslyn/issues/36285) | [AviAvni](https://github.com/AviAvni) | [agocke](https://github.com/agocke) | [mattwar](https://github.com/mattwar) | | [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [Implemented](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | -| [Constant Interpolated Strings](https://github.com/dotnet/csharplang/issues/2951) | main | [Merged into 16.9p3](https://github.com/dotnet/roslyn/pull/49676) | [kevinsun-dev](https://github.com/kevinsun-dev) | [333fred](https://github.com/333fred) | [jaredar](https://github.com/jaredpar), [agocke](https://github.com/agocke) | -| [Mix declarations and variables in deconstruction](https://github.com/dotnet/csharplang/issues/125) | main | [Merged into 16.10](https://github.com/dotnet/roslyn/issues/47746) | [YairHalberstadt ](https://github.com/YairHalberstadt) | [jcouv](https://github.com/jcouv) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [List patterns](https://github.com/dotnet/csharplang/issues/3435) | [list-patterns](https://github.com/dotnet/roslyn/tree/features/list-patterns) | [In Progress](https://github.com/dotnet/roslyn/issues/51289) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | + + +# C# 10.0 + +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | +| [Record structs](https://github.com/dotnet/csharplang/issues/4334) | [record-structs](https://github.com/dotnet/roslyn/tree/features/record-structs) | [Merged into 16.11](https://github.com/dotnet/roslyn/issues/51199) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | +| [Global Using Directive](https://github.com/dotnet/csharplang/issues/3428) | [GlobalUsingDirective](https://github.com/dotnet/roslyn/tree/features/GlobalUsingDirective) | [Merged into 16.11](https://github.com/dotnet/roslyn/issues/51307) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | [AlekseyTs](https://github.com/AlekseyTs) | +| [Improved Definite Assignment](https://github.com/dotnet/csharplang/issues/4465) | [improved-definite-assignment](https://github.com/dotnet/roslyn/tree/features/improved-definite-assignment) | [Merged into 17.0](https://github.com/dotnet/roslyn/issues/51463) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) | +| [Constant Interpolated Strings](https://github.com/dotnet/csharplang/issues/2951) | main | [Merged into 16.9p3](https://github.com/dotnet/roslyn/pull/49676) | [kevinsun-dev](https://github.com/kevinsun-dev) | [333fred](https://github.com/333fred) | [jaredar](https://github.com/jaredpar), [agocke](https://github.com/agocke) | | [Extended property patterns](https://github.com/dotnet/csharplang/issues/4394) | [extended-property-patterns](https://github.com/dotnet/roslyn/tree/features/extended-property-patterns) | [Merged into 17.0](https://github.com/dotnet/roslyn/issues/52468) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | | [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174) | main | [Merged](https://github.com/dotnet/roslyn/issues/52031) | [thomaslevesque](https://github.com/thomaslevesque/) | [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | -| [Source Generator V2 APIs](https://github.com/dotnet/roslyn/issues/51257) | [features/source-generators](https://github.com/dotnet/roslyn/tree/features/source-generators) | [In Progress](https://github.com/dotnet/roslyn/issues/51257) | [chsienki](https://github.com/chsienki/) | [rikkigibson](https://github.com/rikkigibson), [jaredpar](https://github.com/jaredpar), [cston](https://github.com/cston) | N/A | -| [Async method builder override](https://github.com/dotnet/csharplang/issues/1407) | main | [In Progress](https://github.com/dotnet/roslyn/issues/51999) | [jcouv](https://github.com/jcouv) | TBD | [stephentoub](https://github.com/stephentoub) | +| [Source Generator V2 APIs](https://github.com/dotnet/roslyn/issues/51257) | [features/source-generators](https://github.com/dotnet/roslyn/tree/features/source-generators) | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/51257) | [chsienki](https://github.com/chsienki/) | [rikkigibson](https://github.com/rikkigibson), [jaredpar](https://github.com/jaredpar), [cston](https://github.com/cston) | N/A | +| [Mix declarations and variables in deconstruction](https://github.com/dotnet/csharplang/issues/125) | main | [Merged into 16.10](https://github.com/dotnet/roslyn/issues/47746) | [YairHalberstadt ](https://github.com/YairHalberstadt) | [jcouv](https://github.com/jcouv) | [MadsTorgersen](https://github.com/MadsTorgersen) | +| [Async method builder override](https://github.com/dotnet/csharplang/issues/1407) | main | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/51999) | [jcouv](https://github.com/jcouv) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | [stephentoub](https://github.com/stephentoub) | +| [Enhanced `#line` directive](https://github.com/dotnet/csharplang/issues/4747) | main | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/54509) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) | # VB 16.9 diff --git a/docs/features/StaticAbstractMembersInInterfaces.md b/docs/features/StaticAbstractMembersInInterfaces.md new file mode 100644 index 0000000000000..ed49e08974677 --- /dev/null +++ b/docs/features/StaticAbstractMembersInInterfaces.md @@ -0,0 +1,14 @@ +Static Abstract Members In Interfaces +===================================== + +An interface is allowed to specify abstract static members that implementing classes and structs are then +required to provide an explicit or implicit implementation of. The members can be accessed off of type +parameters that are constrained by the interface. + +Proposal: +- https://github.com/dotnet/csharplang/issues/4436 +- https://github.com/dotnet/csharplang/blob/main/proposals/static-abstracts-in-interfaces.md + +Feature branch: https://github.com/dotnet/roslyn/tree/features/StaticAbstractMembersInInterfaces + +Test plan: https://github.com/dotnet/roslyn/issues/52221 \ No newline at end of file diff --git a/docs/features/refout.md b/docs/features/refout.md index 0571529797481..4487d9f006353 100644 --- a/docs/features/refout.md +++ b/docs/features/refout.md @@ -61,13 +61,13 @@ An additional task, called `CopyRefAssembly`, is provided along with the existin As a side-note, `CopyRefAssembly` uses the same assembly resolution/redirection trick as `Csc` and `Vbc`, to avoid type loading problems with `System.IO.FileSystem`. ### CodeAnalysis APIs -Prior to C# 7.1, it was already possible to produce metadata-only assemblies by using `EmitOptions.EmitMetadataOnly`, which is used in IDE scenarios with cross-language dependencies. +Prior to C# 7.1, it was already possible to produce metadata-only assemblies by using `EmitOptions.EmitMetadataOnly`, which is used in IDE scenarios with cross-language dependencies. -With C# 7.1, the compiler now honours the `EmitOptions.IncludePrivateMembers` flag as well. When combined with `EmitMetadataOnly` or a `metadataPeStream` in `Emit`, a ref assembly is produced. +With C# 7.1, the compiler now honours the `EmitOptions.IncludePrivateMembers` flag as well. When combined with `EmitMetadataOnly` or a `metadataPeStream` in `Emit`, a ref assembly is produced. -The diagnostic check for emitting methods lacking a body (`void M();`) is filtered from declaration diagnostics, so such code will successfully emit with `EmitMetadataOnly`. +Method bodies aren't compiled when using `EmitMetadataOnly`. Even the diagnostic check for emitting methods lacking a body (`void M();`) is filtered from declaration diagnostics, so such code will successfully emit with `EmitMetadataOnly`. -Later on, the `EmitOptions.TolerateErrors` flag will allow emitting error types as well. +Later on, the `EmitOptions.TolerateErrors` flag will allow emitting error types as well. `Emit` was modified to produce a new PE section called ".mvid" containing a copy of the MVID, when emitting ref assemblies. This makes it easy for `CopyRefAssembly` to extract and compare MVIDs from ref assemblies. Going back to the 4 driving scenarios: diff --git a/dotnet-tools.json b/dotnet-tools.json index 10d642464725d..6ab0998319770 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -2,7 +2,7 @@ "isRoot": true, "tools": { "dotnet-format": { - "version": "5.0.141503", + "version": "6.0.231801", "commands": [ "dotnet-format" ] diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 1550d473a9f8a..629edf6934c73 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -6,9 +6,9 @@ 7e80445ee82adbf9a8e6ae601ac5e239d982afaa - + https://github.com/dotnet/source-build - c06ae1212bd69e9fe52bed0b0a7d79cc8ea39054 + bf8af9a5202d5016b4bcf3424fac6bac55ef7c8e @@ -18,9 +18,9 @@ 78da7776965b428ff31da8f1ff2cb073506212b7 - + https://github.com/dotnet/roslyn - 5b972bceb846f5d15f991a479e285067a75103e4 + 03a07d1dd606ce11d62c9a595041c4c2d44c39e3 https://github.com/dotnet/arcade diff --git a/eng/Versions.props b/eng/Versions.props index 5b00a7d5603e9..c2b3f72ab58ce 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,7 +8,7 @@ 4 0 0 - 2 + 3 $(MajorVersion).$(MinorVersion).$(PatchVersion) 3.3.3-beta1.21105.3 6.0.0-preview1.21054.10 - 1.0.1-beta1.20623.3 - 3.9.0 + 1.1.0-beta1.21322.2 + 3.10.0 16.10.230 5.0.0-alpha1.19409.1 5.0.0-preview.1.20112.8 - 16.10.1202 + 16.11.2 16.10.31320.204 16.5.0 + @@ -404,6 +407,7 @@ + + <_MaxSupportedLangVersion Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND '$(_TargetFrameworkVersionWithoutV)' == '6.0' AND + '$(_MaxSupportedLangVersion)' == ''">10.0 + $(_MaxSupportedLangVersion) $(_MaxSupportedLangVersion) diff --git a/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets b/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets index 5c035bb1ba9ff..d742060eeeab5 100644 --- a/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets +++ b/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets @@ -50,10 +50,19 @@ + + + - + <_SkipAnalyzers> + <_ImplicitlySkipAnalyzers> + + <_ImplicitlySkipAnalyzers>true + <_SkipAnalyzers>true + + + + + + + + <_LastBuildWithSkipAnalyzers>$(IntermediateOutputPath)$(MSBuildProjectFile).BuildWithSkipAnalyzers + + + + + + + + + + + + - + diff --git a/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb b/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb index 08fbbf38055a0..9dd3584870b2a 100644 --- a/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb +++ b/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb @@ -3758,14 +3758,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend NotInheritable Class BoundObjectInitializerExpression Inherits BoundObjectInitializerExpressionBase - Public Sub New(syntax As SyntaxNode, createTemporaryLocalForInitialization As Boolean, binder As Binder, placeholderOpt As BoundWithLValueExpressionPlaceholder, initializers As ImmutableArray(Of BoundExpression), type As TypeSymbol, Optional hasErrors As Boolean = False) + Public Sub New(syntax As SyntaxNode, createTemporaryLocalForInitialization As Boolean, placeholderOpt As BoundWithLValueExpressionPlaceholder, initializers As ImmutableArray(Of BoundExpression), type As TypeSymbol, Optional hasErrors As Boolean = False) MyBase.New(BoundKind.ObjectInitializerExpression, syntax, placeholderOpt, initializers, type, hasErrors OrElse placeholderOpt.NonNullAndHasErrors() OrElse initializers.NonNullAndHasErrors()) - Debug.Assert(binder IsNot Nothing, "Field 'binder' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Debug.Assert(Not (initializers.IsDefault), "Field 'initializers' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Me._CreateTemporaryLocalForInitialization = createTemporaryLocalForInitialization - Me._Binder = binder Validate() End Sub @@ -3781,21 +3779,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - Private ReadOnly _Binder As Binder - Public ReadOnly Property Binder As Binder - Get - Return _Binder - End Get - End Property - Public Overrides Function Accept(visitor as BoundTreeVisitor) As BoundNode Return visitor.VisitObjectInitializerExpression(Me) End Function - Public Function Update(createTemporaryLocalForInitialization As Boolean, binder As Binder, placeholderOpt As BoundWithLValueExpressionPlaceholder, initializers As ImmutableArray(Of BoundExpression), type As TypeSymbol) As BoundObjectInitializerExpression - If createTemporaryLocalForInitialization <> Me.CreateTemporaryLocalForInitialization OrElse binder IsNot Me.Binder OrElse placeholderOpt IsNot Me.PlaceholderOpt OrElse initializers <> Me.Initializers OrElse type IsNot Me.Type Then - Dim result = New BoundObjectInitializerExpression(Me.Syntax, createTemporaryLocalForInitialization, binder, placeholderOpt, initializers, type, Me.HasErrors) + Public Function Update(createTemporaryLocalForInitialization As Boolean, placeholderOpt As BoundWithLValueExpressionPlaceholder, initializers As ImmutableArray(Of BoundExpression), type As TypeSymbol) As BoundObjectInitializerExpression + If createTemporaryLocalForInitialization <> Me.CreateTemporaryLocalForInitialization OrElse placeholderOpt IsNot Me.PlaceholderOpt OrElse initializers <> Me.Initializers OrElse type IsNot Me.Type Then + Dim result = New BoundObjectInitializerExpression(Me.Syntax, createTemporaryLocalForInitialization, placeholderOpt, initializers, type, Me.HasErrors) result.CopyAttributes(Me) Return result End If @@ -4500,14 +4491,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend NotInheritable Class BoundAsNewLocalDeclarations Inherits BoundLocalDeclarationBase - Public Sub New(syntax As SyntaxNode, localDeclarations As ImmutableArray(Of BoundLocalDeclaration), initializer As BoundExpression, Optional hasErrors As Boolean = False) + Public Sub New(syntax As SyntaxNode, localDeclarations As ImmutableArray(Of BoundLocalDeclaration), initializer As BoundExpression, binder As Binder, Optional hasErrors As Boolean = False) MyBase.New(BoundKind.AsNewLocalDeclarations, syntax, hasErrors OrElse localDeclarations.NonNullAndHasErrors() OrElse initializer.NonNullAndHasErrors()) Debug.Assert(Not (localDeclarations.IsDefault), "Field 'localDeclarations' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Debug.Assert(initializer IsNot Nothing, "Field 'initializer' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") + Debug.Assert(binder IsNot Nothing, "Field 'binder' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Me._LocalDeclarations = localDeclarations Me._Initializer = initializer + Me._Binder = binder End Sub @@ -4525,14 +4518,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Private ReadOnly _Binder As Binder + Public ReadOnly Property Binder As Binder + Get + Return _Binder + End Get + End Property + Public Overrides Function Accept(visitor as BoundTreeVisitor) As BoundNode Return visitor.VisitAsNewLocalDeclarations(Me) End Function - Public Function Update(localDeclarations As ImmutableArray(Of BoundLocalDeclaration), initializer As BoundExpression) As BoundAsNewLocalDeclarations - If localDeclarations <> Me.LocalDeclarations OrElse initializer IsNot Me.Initializer Then - Dim result = New BoundAsNewLocalDeclarations(Me.Syntax, localDeclarations, initializer, Me.HasErrors) + Public Function Update(localDeclarations As ImmutableArray(Of BoundLocalDeclaration), initializer As BoundExpression, binder As Binder) As BoundAsNewLocalDeclarations + If localDeclarations <> Me.LocalDeclarations OrElse initializer IsNot Me.Initializer OrElse binder IsNot Me.Binder Then + Dim result = New BoundAsNewLocalDeclarations(Me.Syntax, localDeclarations, initializer, binder, Me.HasErrors) result.CopyAttributes(Me) Return result End If @@ -4611,13 +4611,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend MustInherit Class BoundFieldOrPropertyInitializer Inherits BoundInitializer - Protected Sub New(kind As BoundKind, syntax as SyntaxNode, memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, Optional hasErrors As Boolean = False) + Protected Sub New(kind As BoundKind, syntax as SyntaxNode, memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, binderOpt As Binder, Optional hasErrors As Boolean = False) MyBase.New(kind, syntax, hasErrors) Debug.Assert(initialValue IsNot Nothing, "Field 'initialValue' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Me._MemberAccessExpressionOpt = memberAccessExpressionOpt Me._InitialValue = initialValue + Me._BinderOpt = binderOpt End Sub @@ -4634,13 +4635,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return _InitialValue End Get End Property + + Private ReadOnly _BinderOpt As Binder + Public ReadOnly Property BinderOpt As Binder + Get + Return _BinderOpt + End Get + End Property End Class Partial Friend NotInheritable Class BoundFieldInitializer Inherits BoundFieldOrPropertyInitializer - Public Sub New(syntax As SyntaxNode, initializedFields As ImmutableArray(Of FieldSymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, Optional hasErrors As Boolean = False) - MyBase.New(BoundKind.FieldInitializer, syntax, memberAccessExpressionOpt, initialValue, hasErrors OrElse memberAccessExpressionOpt.NonNullAndHasErrors() OrElse initialValue.NonNullAndHasErrors()) + Public Sub New(syntax As SyntaxNode, initializedFields As ImmutableArray(Of FieldSymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, binderOpt As Binder, Optional hasErrors As Boolean = False) + MyBase.New(BoundKind.FieldInitializer, syntax, memberAccessExpressionOpt, initialValue, binderOpt, hasErrors OrElse memberAccessExpressionOpt.NonNullAndHasErrors() OrElse initialValue.NonNullAndHasErrors()) Debug.Assert(Not (initializedFields.IsDefault), "Field 'initializedFields' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Debug.Assert(initialValue IsNot Nothing, "Field 'initialValue' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") @@ -4661,9 +4669,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return visitor.VisitFieldInitializer(Me) End Function - Public Function Update(initializedFields As ImmutableArray(Of FieldSymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression) As BoundFieldInitializer - If initializedFields <> Me.InitializedFields OrElse memberAccessExpressionOpt IsNot Me.MemberAccessExpressionOpt OrElse initialValue IsNot Me.InitialValue Then - Dim result = New BoundFieldInitializer(Me.Syntax, initializedFields, memberAccessExpressionOpt, initialValue, Me.HasErrors) + Public Function Update(initializedFields As ImmutableArray(Of FieldSymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, binderOpt As Binder) As BoundFieldInitializer + If initializedFields <> Me.InitializedFields OrElse memberAccessExpressionOpt IsNot Me.MemberAccessExpressionOpt OrElse initialValue IsNot Me.InitialValue OrElse binderOpt IsNot Me.BinderOpt Then + Dim result = New BoundFieldInitializer(Me.Syntax, initializedFields, memberAccessExpressionOpt, initialValue, binderOpt, Me.HasErrors) result.CopyAttributes(Me) Return result End If @@ -4674,8 +4682,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend NotInheritable Class BoundPropertyInitializer Inherits BoundFieldOrPropertyInitializer - Public Sub New(syntax As SyntaxNode, initializedProperties As ImmutableArray(Of PropertySymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, Optional hasErrors As Boolean = False) - MyBase.New(BoundKind.PropertyInitializer, syntax, memberAccessExpressionOpt, initialValue, hasErrors OrElse memberAccessExpressionOpt.NonNullAndHasErrors() OrElse initialValue.NonNullAndHasErrors()) + Public Sub New(syntax As SyntaxNode, initializedProperties As ImmutableArray(Of PropertySymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, binderOpt As Binder, Optional hasErrors As Boolean = False) + MyBase.New(BoundKind.PropertyInitializer, syntax, memberAccessExpressionOpt, initialValue, binderOpt, hasErrors OrElse memberAccessExpressionOpt.NonNullAndHasErrors() OrElse initialValue.NonNullAndHasErrors()) Debug.Assert(Not (initializedProperties.IsDefault), "Field 'initializedProperties' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Debug.Assert(initialValue IsNot Nothing, "Field 'initialValue' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") @@ -4696,9 +4704,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return visitor.VisitPropertyInitializer(Me) End Function - Public Function Update(initializedProperties As ImmutableArray(Of PropertySymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression) As BoundPropertyInitializer - If initializedProperties <> Me.InitializedProperties OrElse memberAccessExpressionOpt IsNot Me.MemberAccessExpressionOpt OrElse initialValue IsNot Me.InitialValue Then - Dim result = New BoundPropertyInitializer(Me.Syntax, initializedProperties, memberAccessExpressionOpt, initialValue, Me.HasErrors) + Public Function Update(initializedProperties As ImmutableArray(Of PropertySymbol), memberAccessExpressionOpt As BoundExpression, initialValue As BoundExpression, binderOpt As Binder) As BoundPropertyInitializer + If initializedProperties <> Me.InitializedProperties OrElse memberAccessExpressionOpt IsNot Me.MemberAccessExpressionOpt OrElse initialValue IsNot Me.InitialValue OrElse binderOpt IsNot Me.BinderOpt Then + Dim result = New BoundPropertyInitializer(Me.Syntax, initializedProperties, memberAccessExpressionOpt, initialValue, binderOpt, Me.HasErrors) result.CopyAttributes(Me) Return result End If @@ -12421,7 +12429,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim placeholderOpt As BoundWithLValueExpressionPlaceholder = DirectCast(Me.Visit(node.PlaceholderOpt), BoundWithLValueExpressionPlaceholder) Dim initializers As ImmutableArray(Of BoundExpression) = Me.VisitList(node.Initializers) Dim type as TypeSymbol = Me.VisitType(node.Type) - Return node.Update(node.CreateTemporaryLocalForInitialization, node.Binder, placeholderOpt, initializers, type) + Return node.Update(node.CreateTemporaryLocalForInitialization, placeholderOpt, initializers, type) End Function Public Overrides Function VisitCollectionInitializerExpression(node As BoundCollectionInitializerExpression) As BoundNode @@ -12506,7 +12514,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitAsNewLocalDeclarations(node As BoundAsNewLocalDeclarations) As BoundNode Dim localDeclarations As ImmutableArray(Of BoundLocalDeclaration) = Me.VisitList(node.LocalDeclarations) Dim initializer As BoundExpression = DirectCast(Me.Visit(node.Initializer), BoundExpression) - Return node.Update(localDeclarations, initializer) + Return node.Update(localDeclarations, initializer, node.Binder) End Function Public Overrides Function VisitDimStatement(node As BoundDimStatement) As BoundNode @@ -12522,13 +12530,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitFieldInitializer(node As BoundFieldInitializer) As BoundNode Dim memberAccessExpressionOpt As BoundExpression = DirectCast(Me.Visit(node.MemberAccessExpressionOpt), BoundExpression) Dim initialValue As BoundExpression = DirectCast(Me.Visit(node.InitialValue), BoundExpression) - Return node.Update(node.InitializedFields, memberAccessExpressionOpt, initialValue) + Return node.Update(node.InitializedFields, memberAccessExpressionOpt, initialValue, node.BinderOpt) End Function Public Overrides Function VisitPropertyInitializer(node As BoundPropertyInitializer) As BoundNode Dim memberAccessExpressionOpt As BoundExpression = DirectCast(Me.Visit(node.MemberAccessExpressionOpt), BoundExpression) Dim initialValue As BoundExpression = DirectCast(Me.Visit(node.InitialValue), BoundExpression) - Return node.Update(node.InitializedProperties, memberAccessExpressionOpt, initialValue) + Return node.Update(node.InitializedProperties, memberAccessExpressionOpt, initialValue, node.BinderOpt) End Function Public Overrides Function VisitParameterEqualsValue(node As BoundParameterEqualsValue) As BoundNode @@ -13663,7 +13671,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitObjectInitializerExpression(node As BoundObjectInitializerExpression, arg As Object) As TreeDumperNode Return New TreeDumperNode("objectInitializerExpression", Nothing, New TreeDumperNode() { New TreeDumperNode("createTemporaryLocalForInitialization", node.CreateTemporaryLocalForInitialization, Nothing), - New TreeDumperNode("binder", node.Binder, Nothing), New TreeDumperNode("placeholderOpt", Nothing, new TreeDumperNode() {Visit(node.PlaceholderOpt, Nothing)}), New TreeDumperNode("initializers", Nothing, From x In node.Initializers Select Visit(x, Nothing)), New TreeDumperNode("type", node.Type, Nothing) @@ -13786,7 +13793,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitAsNewLocalDeclarations(node As BoundAsNewLocalDeclarations, arg As Object) As TreeDumperNode Return New TreeDumperNode("asNewLocalDeclarations", Nothing, New TreeDumperNode() { New TreeDumperNode("localDeclarations", Nothing, From x In node.LocalDeclarations Select Visit(x, Nothing)), - New TreeDumperNode("initializer", Nothing, new TreeDumperNode() {Visit(node.Initializer, Nothing)}) + New TreeDumperNode("initializer", Nothing, new TreeDumperNode() {Visit(node.Initializer, Nothing)}), + New TreeDumperNode("binder", node.Binder, Nothing) }) End Function @@ -13805,7 +13813,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return New TreeDumperNode("fieldInitializer", Nothing, New TreeDumperNode() { New TreeDumperNode("initializedFields", node.InitializedFields, Nothing), New TreeDumperNode("memberAccessExpressionOpt", Nothing, new TreeDumperNode() {Visit(node.MemberAccessExpressionOpt, Nothing)}), - New TreeDumperNode("initialValue", Nothing, new TreeDumperNode() {Visit(node.InitialValue, Nothing)}) + New TreeDumperNode("initialValue", Nothing, new TreeDumperNode() {Visit(node.InitialValue, Nothing)}), + New TreeDumperNode("binderOpt", node.BinderOpt, Nothing) }) End Function @@ -13813,7 +13822,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return New TreeDumperNode("propertyInitializer", Nothing, New TreeDumperNode() { New TreeDumperNode("initializedProperties", node.InitializedProperties, Nothing), New TreeDumperNode("memberAccessExpressionOpt", Nothing, new TreeDumperNode() {Visit(node.MemberAccessExpressionOpt, Nothing)}), - New TreeDumperNode("initialValue", Nothing, new TreeDumperNode() {Visit(node.InitialValue, Nothing)}) + New TreeDumperNode("initialValue", Nothing, new TreeDumperNode() {Visit(node.InitialValue, Nothing)}), + New TreeDumperNode("binderOpt", node.BinderOpt, Nothing) }) End Function diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AsNewLocalDeclarations.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AsNewLocalDeclarations.vb index 7d0feb839fcdf..b4b23ed1b155f 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AsNewLocalDeclarations.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AsNewLocalDeclarations.vb @@ -16,27 +16,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitAsNewLocalDeclarations(node As BoundAsNewLocalDeclarations) As BoundNode Dim builder = ArrayBuilder(Of BoundStatement).GetInstance() - ' The initializer expression may contain placeholder values and typically they are replaced with bound locals that - ' get created in the local rewriter when rewriting the expression. - ' - ' There is one case where the placeholder does not get replaced by a bound temporary and needs to be replaced by - ' the currently declared local. This is the case for an object creation expression with a object initializer if it's - ' used in an AsNew declaration and the type is a value type, e.g.: - ' Dim x As New ValType() With {...} - ' or - ' Dim x, y As New ValType() With {...} - ' Because only this method knows the bound local that the placeholder will be replaced with, we need to fill - ' the replacement map here, before rewriting the local declaration (there is a special case, because the first - ' example will be bound to a BoundLocalDeclaration, see LocalRewriter.VisitLocalDeclaration for the special case). - ' - Dim objectInitializer As BoundObjectInitializerExpression = GetBoundObjectInitializerFromInitializer(node.Initializer) - Dim localDeclarations = node.LocalDeclarations For declarationIndex = 0 To localDeclarations.Length - 1 Dim localDeclaration = localDeclarations(declarationIndex) Debug.Assert(localDeclaration.InitializerOpt Is Nothing) - Dim rewrittenInitializer As BoundNode = Nothing + Dim rewrittenInitializer As BoundNode Dim localSymbol = localDeclaration.LocalSymbol Dim staticLocalBackingFields As KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField) = Nothing @@ -45,52 +30,61 @@ Namespace Microsoft.CodeAnalysis.VisualBasic staticLocalBackingFields = CreateBackingFieldsForStaticLocal(localSymbol, hasInitializer:=True) End If + Dim initializerToRewrite As BoundExpression + + If declarationIndex = 0 Then + initializerToRewrite = node.Initializer + Else + ' For all variables except the first one we rebind the initializer + ' and throw away diagnostics, those are supposed to be reported in the first + ' binding and don't need to be duplicated as Dev10/11 does + ' + ' Note that we have to rebind the initializers because current implementation of lambda + ' rewriter does not handle correctly blocks and locals which are reused in bound tree. + ' Actually it seems to heavily rely on an assumption that the bound tree is a tree, not + ' a DAG, with just minor deviations. Thus, the natural way of just rewriting bound + ' initializer stored in node.Initializer simply does not work because rewriting *may* + ' keep many blocks and locals unchanged and reuse them in all rewritten initializers, + ' in which case if we have a lambda inside the initializer lambda rewriter simply throws. + ' + ' Another option to satisfy lambda rewriter would be to deep-clone bound tree, but + ' in this case we will also have to clone all locals and all references to locals. + ' We might want to look into this option later. + + Debug.Assert(node.Syntax IsNot Nothing) + Debug.Assert(node.Syntax.Kind = SyntaxKind.VariableDeclarator) + + Dim varDecl = DirectCast(node.Syntax, VariableDeclaratorSyntax) + Dim asNew = DirectCast(varDecl.AsClause, AsNewClauseSyntax) + + ' Rebind and discard diagnostics + initializerToRewrite = node.Binder.BindVariableDeclaration(varDecl, varDecl.Names(declarationIndex), asNew, Nothing, BindingDiagnosticBag.Discarded, skipAsNewInitializer:=False).InitializerOpt + End If + + ' The initializer expression may contain placeholder values and typically they are replaced with bound locals that + ' get created in the local rewriter when rewriting the expression. + ' + ' There is one case where the placeholder does not get replaced by a bound temporary and needs to be replaced by + ' the currently declared local. This is the case for an object creation expression with a object initializer if it's + ' used in an AsNew declaration and the type is a value type, e.g.: + ' Dim x As New ValType() With {...} + ' or + ' Dim x, y As New ValType() With {...} + ' Because only this method knows the bound local that the placeholder will be replaced with, we need to fill + ' the replacement map here, before rewriting the local declaration (there is a special case, because the first + ' example will be bound to a BoundLocalDeclaration, see LocalRewriter.VisitLocalDeclaration for the special case). + ' + Dim objectInitializer As BoundObjectInitializerExpression = GetBoundObjectInitializerFromInitializer(initializerToRewrite) + + ' rewrite the initializer for each declared variable because the initializer may contain placeholders that need ' to be replaced with the current bound local + If objectInitializer IsNot Nothing Then Debug.Assert(objectInitializer.PlaceholderOpt IsNot Nothing) - Dim initializerToRewrite As BoundExpression = node.Initializer Dim placeholder As BoundWithLValueExpressionPlaceholder = objectInitializer.PlaceholderOpt - If declarationIndex > 0 Then - - ' For all variables except the first one we rebind the initializer - ' and throw away diagnostics, those are supposed to be reported in the first - ' binding and don't need to be duplicated as Dev10/11 does - ' - ' Note that we have to rebind the initializers because current implementation of lambda - ' rewriter does not handle correctly blocks and locals which are reused in bound tree. - ' Actually it seems to heavily rely on an assumption that the bound tree is a tree, not - ' a DAG, with just minor deviations. Thus, the natural way of just rewriting bound - ' initializer stored in node.Initializer simply does not work because rewriting *may* - ' keep many blocks and locals unchanged and reuse them in all rewritten initializers, - ' in which case if we have a lambda inside the initializer lambda rewriter simply throws. - ' - ' Another option to satisfy lambda rewriter would be to deep-clone bound tree, but - ' in this case we will also have to clone all locals and all references to locals. - ' We might want to look into this option later. - - Debug.Assert(node.Syntax IsNot Nothing) - Debug.Assert(node.Syntax.Kind = SyntaxKind.VariableDeclarator) - Dim asNew = DirectCast(DirectCast(node.Syntax, VariableDeclaratorSyntax).AsClause, AsNewClauseSyntax) - - Dim objectCreationExpressionSyntax = DirectCast(asNew.NewExpression, ObjectCreationExpressionSyntax) - Dim localType As TypeSymbol = localSymbol.Type - - ' New placeholder - placeholder = New BoundWithLValueExpressionPlaceholder(asNew, localType) - placeholder.SetWasCompilerGenerated() - - ' Rebind and discard diagnostics - initializerToRewrite = objectInitializer.Binder.BindObjectCreationExpression(asNew.Type, - objectCreationExpressionSyntax.ArgumentList, - localType, - objectCreationExpressionSyntax, - BindingDiagnosticBag.Discarded, - placeholder) - End If - If Not objectInitializer.CreateTemporaryLocalForInitialization Then AddPlaceholderReplacement(placeholder, VisitExpressionNode(New BoundLocal(localDeclaration.Syntax, @@ -105,7 +99,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End If Else - rewrittenInitializer = Me.VisitAndGenerateObjectCloneIfNeeded(node.Initializer) + rewrittenInitializer = Me.VisitAndGenerateObjectCloneIfNeeded(initializerToRewrite) End If Dim initialization = RewriteLocalDeclarationAsInitializer( diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_FieldOrPropertyInitializer.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_FieldOrPropertyInitializer.vb index 810ac06c4cd48..6ff410c45917a 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_FieldOrPropertyInitializer.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_FieldOrPropertyInitializer.vb @@ -54,23 +54,50 @@ Namespace Microsoft.CodeAnalysis.VisualBasic meReferenceOpt.SetWasCompilerGenerated() End If - Dim objectInitializer As BoundObjectInitializerExpression = Nothing - Dim createTemporary = True - If node.InitialValue.Kind = BoundKind.ObjectCreationExpression OrElse node.InitialValue.Kind = BoundKind.NewT Then - Dim objectCreationExpression = DirectCast(node.InitialValue, BoundObjectCreationExpressionBase) - If objectCreationExpression.InitializerOpt IsNot Nothing AndAlso - objectCreationExpression.InitializerOpt.Kind = BoundKind.ObjectInitializerExpression Then - objectInitializer = DirectCast(objectCreationExpression.InitializerOpt, BoundObjectInitializerExpression) - createTemporary = objectInitializer.CreateTemporaryLocalForInitialization - End If - End If - Dim instrument As Boolean = Me.Instrument(node) For symbolIndex = 0 To initializedSymbols.Length - 1 Dim symbol = initializedSymbols(symbolIndex) Dim accessExpression As BoundExpression + Dim initialValueToRewrite As BoundExpression + + If symbolIndex = 0 Then + initialValueToRewrite = node.InitialValue + Else + ' For all variables except the first one we rebind the initializer + ' and throw away diagnostics, those are supposed to be reported in the first + ' binding and don't need to be duplicated + ' + ' Note that we have to rebind the initializers because current implementation of lambda + ' rewriter does not handle correctly blocks and locals which are reused in bound tree. + ' Actually it seems to heavily rely on an assumption that the bound tree is a tree, not + ' a DAG, with just minor deviations. Thus, the natural way of just rewriting bound + ' initializer stored in node.Initializer simply does not work because rewriting *may* + ' keep many blocks and locals unchanged and reuse them in all rewritten initializers, + ' in which case if we have a lambda inside the initializer lambda rewriter simply throws. + + If symbol.Kind = SymbolKind.Field Then + ' Rebind and discard diagnostics + initialValueToRewrite = node.BinderOpt.BindFieldInitializerExpression(syntax, DirectCast(symbol, FieldSymbol), BindingDiagnosticBag.Discarded) + Else + ' Rebind and discard diagnostics + initialValueToRewrite = node.BinderOpt.BindPropertyInitializerExpression(syntax, DirectCast(symbol, PropertySymbol), BindingDiagnosticBag.Discarded) + End If + End If + + Dim objectInitializer As BoundObjectInitializerExpression = Nothing + Dim createTemporary = True + + If initialValueToRewrite.Kind = BoundKind.ObjectCreationExpression OrElse initialValueToRewrite.Kind = BoundKind.NewT Then + Dim objectCreationExpression = DirectCast(initialValueToRewrite, BoundObjectCreationExpressionBase) + If objectCreationExpression.InitializerOpt IsNot Nothing AndAlso + objectCreationExpression.InitializerOpt.Kind = BoundKind.ObjectInitializerExpression Then + objectInitializer = DirectCast(objectCreationExpression.InitializerOpt, BoundObjectInitializerExpression) + createTemporary = objectInitializer.CreateTemporaryLocalForInitialization + End If + End If + ' if there are more than one symbol we need to create a field or property access for each of them If initializedSymbols.Length > 1 Then If symbol.Kind = SymbolKind.Field Then @@ -102,14 +129,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' we need to replace the placeholder with it, so add it to the replacement map AddPlaceholderReplacement(objectInitializer.PlaceholderOpt, accessExpression) - rewrittenStatement = VisitExpressionNode(node.InitialValue).ToStatement + rewrittenStatement = VisitExpressionNode(initialValueToRewrite).ToStatement RemovePlaceholderReplacement(objectInitializer.PlaceholderOpt) Else ' in all other cases we want the initial value be assigned to the member (field or property) rewrittenStatement = VisitExpression(New BoundAssignmentOperator(syntax, accessExpression, - node.InitialValue, + initialValueToRewrite, suppressObjectClone:=False)).ToStatement End If diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreation.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreation.vb index 2997b04509a1a..3dfbba5c3e195 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreation.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreation.vb @@ -388,7 +388,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return ReplaceObjectOrCollectionInitializer( rewrittenObjectCreationExpression, node.Update(node.CreateTemporaryLocalForInitialization, - node.Binder, node.PlaceholderOpt, newInitializers.AsImmutableOrNull(), node.Type)) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb index 79265b51e293d..d0eb4b39bfa31 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb @@ -26,13 +26,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' A mapping from every local variable to its replacement local variable. Local variables ''' are replaced when their types change due to being inside of a lambda within a generic method. ''' - Protected ReadOnly LocalMap As Dictionary(Of LocalSymbol, LocalSymbol) = New Dictionary(Of LocalSymbol, LocalSymbol)() + Protected ReadOnly LocalMap As Dictionary(Of LocalSymbol, LocalSymbol) = New Dictionary(Of LocalSymbol, LocalSymbol)(ReferenceEqualityComparer.Instance) ''' ''' A mapping from every parameter to its replacement parameter. Local variables ''' are replaced when their types change due to being inside of a lambda. ''' - Protected ReadOnly ParameterMap As Dictionary(Of ParameterSymbol, ParameterSymbol) = New Dictionary(Of ParameterSymbol, ParameterSymbol)() + Protected ReadOnly ParameterMap As Dictionary(Of ParameterSymbol, ParameterSymbol) = New Dictionary(Of ParameterSymbol, ParameterSymbol)(ReferenceEqualityComparer.Instance) Protected ReadOnly PlaceholderReplacementMap As New Dictionary(Of BoundValuePlaceholderBase, BoundExpression) diff --git a/src/Compilers/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.vbproj b/src/Compilers/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.vbproj index 840b7ecd76e75..a4e3694d5dca4 100644 --- a/src/Compilers/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.vbproj +++ b/src/Compilers/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.vbproj @@ -8,7 +8,7 @@ ..\BasicCodeAnalysisRules.ruleset true - partial + partial true diff --git a/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicGeneratorDriver.vb b/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicGeneratorDriver.vb index d8ad0c0932ccf..3c4cd057ef2f8 100644 --- a/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicGeneratorDriver.vb +++ b/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicGeneratorDriver.vb @@ -16,7 +16,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Sub Friend Sub New(parseOptions As VisualBasicParseOptions, generators As ImmutableArray(Of ISourceGenerator), optionsProvider As AnalyzerConfigOptionsProvider, additionalTexts As ImmutableArray(Of AdditionalText)) - MyBase.New(parseOptions, generators, optionsProvider, additionalTexts) + MyBase.New(parseOptions, generators, optionsProvider, additionalTexts, enableIncremental:=False) End Sub Friend Overrides ReadOnly Property MessageProvider As CommonMessageProvider @@ -34,7 +34,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Function Public Shared Function Create(generators As ImmutableArray(Of ISourceGenerator), Optional additionalTexts As ImmutableArray(Of AdditionalText) = Nothing, Optional parseOptions As VisualBasicParseOptions = Nothing, Optional analyzerConfigOptionsProvider As AnalyzerConfigOptionsProvider = Nothing) As VisualBasicGeneratorDriver - Return New VisualBasicGeneratorDriver(parseOptions, generators, analyzerConfigOptionsProvider, additionalTexts) + Return New VisualBasicGeneratorDriver(parseOptions, generators, If(analyzerConfigOptionsProvider, CompilerAnalyzerConfigOptionsProvider.Empty), additionalTexts.NullToEmpty()) End Function Friend Overrides Function CreateSourcesCollection() As AdditionalSourcesCollection diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.vb index a1dee50b3df56..3e3985ba83fea 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.vb @@ -43,35 +43,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE End Function ''' - ''' This override changes two things: - ''' 1) Return type arguments instead of type parameters. - ''' 2) Handle non-PE types. + ''' This override can handle non-PE types. ''' Protected Overrides Function GetGenericTypeParamSymbol(position As Integer) As TypeSymbol Dim peType As PENamedTypeSymbol = TryCast(Me._containingType, PENamedTypeSymbol) If peType IsNot Nothing Then - While peType IsNot Nothing AndAlso (peType.MetadataArity - peType.Arity) > position - peType = TryCast(peType.ContainingSymbol, PENamedTypeSymbol) - End While - - If peType Is Nothing OrElse peType.MetadataArity <= position Then - Return New UnsupportedMetadataTypeSymbol(VBResources.PositionOfTypeParameterTooLarge) - End If - - position -= peType.MetadataArity - peType.Arity - Debug.Assert(position >= 0 AndAlso position < peType.Arity) - - Return peType.TypeArgumentsNoUseSiteDiagnostics(position) + Return MyBase.GetGenericTypeParamSymbol(position) End If Dim namedType As NamedTypeSymbol = TryCast(Me._containingType, NamedTypeSymbol) If namedType IsNot Nothing Then Dim cumulativeArity As Integer - Dim typeArgument As TypeSymbol = Nothing + Dim typeParameter As TypeParameterSymbol = Nothing - GetGenericTypeArgumentSymbol(position, namedType, cumulativeArity, typeArgument) - If typeArgument IsNot Nothing Then - Return typeArgument + GetGenericTypeParameterSymbol(position, namedType, cumulativeArity, typeParameter) + If typeParameter IsNot Nothing Then + Return typeParameter Else Debug.Assert(cumulativeArity <= position) Return New UnsupportedMetadataTypeSymbol(VBResources.PositionOfTypeParameterTooLarge) @@ -81,7 +68,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Return New UnsupportedMetadataTypeSymbol(VBResources.AssociatedTypeDoesNotHaveTypeParameters) End Function - Private Shared Sub GetGenericTypeArgumentSymbol(position As Integer, namedType As NamedTypeSymbol, ByRef cumulativeArity As Integer, ByRef typeArgument As TypeSymbol) + Private Shared Sub GetGenericTypeParameterSymbol(position As Integer, namedType As NamedTypeSymbol, ByRef cumulativeArity As Integer, ByRef typeArgument As TypeParameterSymbol) cumulativeArity = namedType.Arity typeArgument = Nothing @@ -91,29 +78,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE If containingType IsNot Nothing Then Dim containingTypeCumulativeArity As Integer - GetGenericTypeArgumentSymbol(position, containingType, containingTypeCumulativeArity, typeArgument) + GetGenericTypeParameterSymbol(position, containingType, containingTypeCumulativeArity, typeArgument) cumulativeArity += containingTypeCumulativeArity arityOffset = containingTypeCumulativeArity End If If arityOffset <= position AndAlso position < cumulativeArity Then Debug.Assert(typeArgument Is Nothing) - typeArgument = namedType.TypeArgumentsNoUseSiteDiagnostics(position - arityOffset) + typeArgument = namedType.TypeParameters(position - arityOffset) End If End Sub ''' - ''' Search through the members of a given type symbol to find the method that matches a particular signature. + ''' Search through the members of the type symbol to find the method that matches a particular signature. ''' - ''' Type containing the desired method symbol. ''' A MemberRef handle that can be used to obtain the name and signature of the method ''' True to only return a method. ''' The matching method symbol, or null if the inputs do not correspond to a valid method. - Friend Function FindMember(targetTypeSymbol As TypeSymbol, memberRef As MemberReferenceHandle, methodsOnly As Boolean) As Symbol - If targetTypeSymbol Is Nothing Then - Return Nothing - End If - + Friend Function FindMember(memberRef As MemberReferenceHandle, methodsOnly As Boolean) As Symbol Try Dim memberName As String = [Module].GetMemberRefNameOrThrow(memberRef) Dim signatureHandle = [Module].GetSignatureOrThrow(memberRef) @@ -124,7 +106,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Case SignatureCallingConvention.Default, SignatureCallingConvention.VarArgs Dim typeParamCount As Integer Dim targetParamInfo As ParamInfo(Of TypeSymbol)() = Me.DecodeSignatureParametersOrThrow(signaturePointer, signatureHeader, typeParamCount) - Return FindMethodBySignature(targetTypeSymbol, memberName, signatureHeader, typeParamCount, targetParamInfo) + Return FindMethodBySignature(_containingType, memberName, signatureHeader, typeParamCount, targetParamInfo) Case SignatureKind.Field If methodsOnly Then @@ -134,7 +116,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Dim customModifiers As ImmutableArray(Of ModifierInfo(Of TypeSymbol)) = Nothing Dim type As TypeSymbol = Me.DecodeFieldSignature(signaturePointer, customModifiers) - Return FindFieldBySignature(targetTypeSymbol, memberName, customModifiers, type) + Return FindFieldBySignature(_containingType, memberName, customModifiers, type) Case Else ' error diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MetadataDecoder.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MetadataDecoder.vb index 012387b04a03d..b5c1a53d2eada 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MetadataDecoder.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/MetadataDecoder.vb @@ -458,8 +458,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE ' We're going to use a special decoder that can generate usable symbols for type parameters without full context. ' (We're not just using a different type - we're also changing the type context.) - Dim memberRefDecoder = New MemberRefMetadataDecoder(moduleSymbol, targetTypeSymbol) - Return memberRefDecoder.FindMember(targetTypeSymbol, memberRef, methodsOnly) + Dim memberRefDecoder = New MemberRefMetadataDecoder(moduleSymbol, targetTypeSymbol.OriginalDefinition) + + Dim definition = memberRefDecoder.FindMember(memberRef, methodsOnly) + + If definition IsNot Nothing AndAlso Not targetTypeSymbol.IsDefinition Then + Return definition.AsMember(DirectCast(targetTypeSymbol, NamedTypeSymbol)) + End If + + Return definition End Function Protected Overrides Sub EnqueueTypeSymbolInterfacesAndBaseTypes(typeDefsToSearch As Queue(Of TypeDefinitionHandle), typeSymbolsToSearch As Queue(Of TypeSymbol), typeSymbol As TypeSymbol) diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb index 02e4d5e75dc07..8d6930537561a 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb @@ -1187,7 +1187,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Try For Each methodDef In [module].GetMethodsOfTypeOrThrow(_handle) - If [module].ShouldImportMethod(methodDef, moduleSymbol.ImportOptions) Then + If [module].ShouldImportMethod(_handle, methodDef, moduleSymbol.ImportOptions) Then methods.Add(methodDef, New PEMethodSymbol(moduleSymbol, Me, methodDef)) End If Next @@ -1207,8 +1207,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Dim methods = [module].GetPropertyMethodsOrThrow(propertyDef) - Dim getMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, methods.Getter) - Dim setMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, methods.Setter) + Dim getMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, _handle, methods.Getter) + Dim setMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, _handle, methods.Setter) If (getMethod IsNot Nothing) OrElse (setMethod IsNot Nothing) Then members.Add(PEPropertySymbol.Create(moduleSymbol, Me, propertyDef, getMethod, setMethod)) @@ -1230,9 +1230,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Dim methods = [module].GetEventMethodsOrThrow(eventRid) ' NOTE: C# ignores all other accessors (most notably, raise/fire). - Dim addMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, methods.Adder) - Dim removeMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, methods.Remover) - Dim raiseMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, methods.Raiser) + Dim addMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, _handle, methods.Adder) + Dim removeMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, _handle, methods.Remover) + Dim raiseMethod = GetAccessorMethod(moduleSymbol, methodHandleToSymbol, _handle, methods.Raiser) ' VB ignores events that do not have both Add and Remove. If (addMethod IsNot Nothing) AndAlso (removeMethod IsNot Nothing) Then @@ -1245,14 +1245,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE End Try End Sub - Private Shared Function GetAccessorMethod(moduleSymbol As PEModuleSymbol, methodHandleToSymbol As Dictionary(Of MethodDefinitionHandle, PEMethodSymbol), methodDef As MethodDefinitionHandle) As PEMethodSymbol + Private Shared Function GetAccessorMethod(moduleSymbol As PEModuleSymbol, methodHandleToSymbol As Dictionary(Of MethodDefinitionHandle, PEMethodSymbol), typeDef As TypeDefinitionHandle, methodDef As MethodDefinitionHandle) As PEMethodSymbol If methodDef.IsNil Then Return Nothing End If Dim method As PEMethodSymbol = Nothing Dim found As Boolean = methodHandleToSymbol.TryGetValue(methodDef, method) - Debug.Assert(found OrElse Not moduleSymbol.Module.ShouldImportMethod(methodDef, moduleSymbol.ImportOptions)) + Debug.Assert(found OrElse Not moduleSymbol.Module.ShouldImportMethod(typeDef, methodDef, moduleSymbol.ImportOptions)) Return method End Function diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb index 59ce74a4b4a2c..46e7c7e6a1bb2 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb @@ -884,6 +884,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting End Function Public Function Retarget(method As MethodSymbol, retargetedMethodComparer As IEqualityComparer(Of MethodSymbol)) As MethodSymbol + Debug.Assert(method Is method.ConstructedFrom) + If method.ContainingModule Is Me.UnderlyingModule AndAlso method.IsDefinition Then Return DirectCast(SymbolMap.GetOrAdd(method, _retargetingModule._createRetargetingMethod), RetargetingMethodSymbol) End If @@ -891,10 +893,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Dim containingType = method.ContainingType Dim retargetedType = Retarget(containingType, RetargetOptions.RetargetPrimitiveTypesByName) + If retargetedType Is containingType Then + Return method + End If + + If Not containingType.IsDefinition Then + Debug.Assert(Not retargetedType.IsDefinition) + + Dim retargetedDefinition = Retarget(method.OriginalDefinition, retargetedMethodComparer) + + If retargetedDefinition Is Nothing Then + Return Nothing + End If + + Return retargetedDefinition.AsMember(retargetedType) + End If + + Debug.Assert(retargetedType.IsDefinition) + ' NB: may return null if the method cannot be found in the retargeted type (e.g. removed in a subsequent version) - Return If(retargetedType Is containingType, - method, - FindMethodInRetargetedType(method, retargetedType, retargetedMethodComparer)) + Return FindMethodInRetargetedType(method, retargetedType, retargetedMethodComparer) End Function Private Function FindMethodInRetargetedType(method As MethodSymbol, retargetedType As NamedTypeSymbol, retargetedMethodComparer As IEqualityComparer(Of MethodSymbol)) As MethodSymbol @@ -904,8 +922,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Private Class RetargetedTypeMethodFinder Inherits RetargetingSymbolTranslator - Private Sub New(retargetingModule As RetargetingModuleSymbol) + Private ReadOnly _retargetedType As NamedTypeSymbol + Private ReadOnly _toFind As MethodSymbol + + Private Sub New(retargetingModule As RetargetingModuleSymbol, retargetedType As NamedTypeSymbol, toFind As MethodSymbol) MyBase.New(retargetingModule) + + _retargetedType = retargetedType + _toFind = toFind End Sub Public Shared Function Find( @@ -918,7 +942,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Return Nothing End If - If Not method.IsGenericMethod Then + If Not method.IsGenericMethod AndAlso Not retargetedType.IsGenericType Then Return FindWorker(translator, method, retargetedType, retargetedMethodComparer) End If @@ -926,9 +950,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting ' among members of a type, constructed methods are never returned through GetMembers API. Debug.Assert(method Is method.ConstructedFrom) - ' A generic method needs special handling because its signature is very likely - ' to refer to method's type parameters. - Dim finder = New RetargetedTypeMethodFinder(translator._retargetingModule) + ' A generic method or a method in generic type needs special handling because its signature is very likely + ' to refer to method's or type's type parameters. + Dim finder = New RetargetedTypeMethodFinder(translator._retargetingModule, retargetedType, method) Return FindWorker(finder, method, retargetedType, retargetedMethodComparer) End Function @@ -977,15 +1001,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting End Function Public Overrides Function Retarget(typeParameter As TypeParameterSymbol) As TypeParameterSymbol - If typeParameter.ContainingModule Is Me.UnderlyingModule Then - Return MyBase.Retarget(typeParameter) + If typeParameter.TypeParameterKind = TypeParameterKind.Method Then + Debug.Assert(typeParameter.ContainingSymbol Is _toFind) + + ' The method symbol we are building will be using IndexedTypeParameterSymbols as + ' its type parameters, therefore, we should return them here as well. + Return IndexedTypeParameterSymbol.GetTypeParameter(typeParameter.Ordinal) End If - Debug.Assert(typeParameter.TypeParameterKind = TypeParameterKind.Method) + Dim containingType As NamedTypeSymbol = _toFind.ContainingType + Dim retargetedContainingType As NamedTypeSymbol = _retargetedType + + Do + If containingType Is typeParameter.ContainingSymbol Then + Return retargetedContainingType.TypeParameters(typeParameter.Ordinal) + End If + + containingType = containingType.ContainingType + retargetedContainingType = retargetedContainingType.ContainingType + Loop While containingType IsNot Nothing - ' The method symbol we are building will be using IndexedTypeParameterSymbols as - ' its type parameters, therefore, we should return them here as well. - Return IndexedTypeParameterSymbol.GetTypeParameter(typeParameter.Ordinal) + Throw ExceptionUtilities.Unreachable End Function End Class diff --git a/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedClonedTypeParameterSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedClonedTypeParameterSymbol.vb index 85122d83b1244..d7a9a545059da 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedClonedTypeParameterSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedClonedTypeParameterSymbol.vb @@ -45,6 +45,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols _correspondingMethodTypeParameter = correspondingMethodTypeParameter _name = name _typeMapFactory = typeMapFactory + + Debug.Assert(Me.TypeParameterKind = If(TypeOf Me.ContainingSymbol Is MethodSymbol, TypeParameterKind.Method, + If(TypeOf Me.ContainingSymbol Is NamedTypeSymbol, TypeParameterKind.Type, + TypeParameterKind.Cref)), + $"Container is {Me.ContainingSymbol?.Kind}, TypeParameterKind is {Me.TypeParameterKind}") End Sub Public Overrides ReadOnly Property TypeParameterKind As TypeParameterKind diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Wrapped/WrappedTypeParameterSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Wrapped/WrappedTypeParameterSymbol.vb index c9cc775bfcd3d..a98e82937f542 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Wrapped/WrappedTypeParameterSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Wrapped/WrappedTypeParameterSymbol.vb @@ -88,6 +88,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Public Sub New(underlyingTypeParameter As TypeParameterSymbol) Debug.Assert(underlyingTypeParameter IsNot Nothing) Me._underlyingTypeParameter = underlyingTypeParameter + + Debug.Assert(Me.TypeParameterKind = If(TypeOf Me.ContainingSymbol Is MethodSymbol, TypeParameterKind.Method, + If(TypeOf Me.ContainingSymbol Is NamedTypeSymbol, TypeParameterKind.Type, + TypeParameterKind.Cref)), + $"Container is {Me.ContainingSymbol?.Kind}, TypeParameterKind is {Me.TypeParameterKind}") End Sub Public Overrides Function GetDocumentationCommentXml(Optional preferredCulture As CultureInfo = Nothing, Optional expandIncludes As Boolean = False, Optional cancellationToken As CancellationToken = Nothing) As String diff --git a/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb b/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb index 8de7ec862a34c..b18da30f4d268 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb @@ -2309,4 +2309,105 @@ BC37254: Public sign was specified and requires a public key, but no public key ) End Sub + + + + Public Sub IVT_Circularity(parseOptions As VisualBasicParseOptions) + Dim other As VisualBasicCompilation = CreateCompilation( + + +Public MustInherit Class TestBaseClass + Friend Overridable ReadOnly Property SupportSvgImages As Object + Get + Return True + End Get + End Property +End Class +]]> + +, options:=TestOptions.SigningReleaseDll, parseOptions:=parseOptions) + + other.VerifyDiagnostics() + + Dim c2 As VisualBasicCompilation = CreateCompilation( + + + + +]]> + +, {New VisualBasicCompilationReference(other)}, options:=TestOptions.SigningReleaseDll, parseOptions:=parseOptions) + + c2.AssertTheseDiagnostics( + ~~~~~~ +]]>) + End Sub + + + + + Public Sub IVT_Circularity_AttributeReferencesProperty(parseOptions As VisualBasicParseOptions) + Dim other As VisualBasicCompilation = CreateCompilation( + + +Public MustInherit Class TestBaseClass + Friend Overridable ReadOnly Property SupportSvgImages As Object + Get + Return True + End Get + End Property +End Class + +Public Class MyAttribute + Inherits System.Attribute + + Public Sub New(s As String) + End Sub +End Class +]]> + +, options:=TestOptions.SigningReleaseDll, parseOptions:=parseOptions) + + other.VerifyDiagnostics() + + Dim c2 As VisualBasicCompilation = CreateCompilation( + + + + +]]> + +, {New VisualBasicCompilationReference(other)}, options:=TestOptions.SigningReleaseDll, parseOptions:=parseOptions) + + c2.AssertNoDiagnostics() + End Sub + End Class diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb index 1e1e3abbe0b7d..d1a70a3446e3e 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb @@ -222,7 +222,7 @@ End Class Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Sub @@ -435,9 +435,9 @@ End Module Row(7, TableIndex.StandAloneSig, EditAndContinueOperation.Default), Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(11, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(18, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(19, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(20, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Sub diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.vb index 49aca9d89d25c..fc4bd161e65f5 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.vb @@ -911,7 +911,8 @@ End Class Using md1 = diff1.GetMetadata() CheckEncLogDefinitions(md1.Reader, Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), - Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default)) + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using End Using End Sub @@ -958,7 +959,9 @@ End Class Using md1 = diff1.GetMetadata() CheckEncLogDefinitions(md1.Reader, Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), - Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default)) + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using End Using End Sub @@ -1038,15 +1041,15 @@ End Class Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(9, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(12, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(19, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(20, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(21, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(22, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(23, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(24, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(25, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(26, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(27, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(17, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using End Using End Sub @@ -1101,9 +1104,9 @@ End Class Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -1250,9 +1253,9 @@ End Class Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -1500,9 +1503,9 @@ End Class Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -1614,9 +1617,9 @@ End Class Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -1731,9 +1734,9 @@ End Class Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -1844,9 +1847,9 @@ End Class Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -1987,9 +1990,9 @@ End Class Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(14, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -2134,9 +2137,9 @@ End Class Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -2296,9 +2299,9 @@ End Class Row(7, TableIndex.Field, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -2461,9 +2464,9 @@ End Class Row(7, TableIndex.Field, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -2622,9 +2625,9 @@ End Class Row(6, TableIndex.Field, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Using diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " @@ -2930,12 +2933,12 @@ End Class Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(9, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(19, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(20, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(21, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(22, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(23, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(24, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) diff1.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " { @@ -3048,9 +3051,9 @@ End Class Row(21, TableIndex.Field, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(25, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(26, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(27, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) diff2.VerifyIL("C.VB$StateMachine_1_F.MoveNext()", " { @@ -3167,12 +3170,12 @@ End Class Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(9, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(12, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(28, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(29, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(30, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(31, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(32, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(33, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(17, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Sub @@ -3444,12 +3447,12 @@ End Class Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(9, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(12, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(19, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(20, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(21, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(22, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(23, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(24, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) diff1.VerifyIL("C.VB$StateMachine_4_F.MoveNext()", " { @@ -3603,9 +3606,9 @@ End Class Row(15, TableIndex.Field, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(9, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(25, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(26, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(27, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(13, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) diff2.VerifyIL("C.VB$StateMachine_4_F.MoveNext()", " { @@ -3765,12 +3768,12 @@ End Class Row(7, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(12, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(15, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(28, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(29, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(30, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(31, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(32, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(33, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(12, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(15, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(17, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) End Sub diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb index 070a4891db66b..98ce6ab7d33a6 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb @@ -2164,5 +2164,223 @@ In getter ]]>) End Sub + + + Public Sub Issue53593_1() + + Dim compilationDef = + + +Public Class C0 + + Public a, b As New C1((Function(n) n + 1)(1)) + + Shared Sub Main() + Dim x as New C0() + System.Console.Write(x.a.F1) + System.Console.Write(x.b.F1) + End Sub +End Class + +Public Class C1 + + Public F1 as Integer + + Sub New(a As Integer) + F1 = a + End Sub +End Class + + + + Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40AndVBRuntime(compilationDef, TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, expectedOutput:="22") + End Sub + + + + Public Sub Issue53593_2() + + Dim compilationDef = + + +Public Class C0 + + Shared Sub Main() + Dim a, b As New C1((Function(n) n + 1)(1)) + System.Console.Write(a.F1) + System.Console.Write(b.F1) + End Sub +End Class + +Public Class C1 + + Public F1 as Integer + + Sub New(a As Integer) + F1 = a + End Sub +End Class + + + + Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40AndVBRuntime(compilationDef, TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, expectedOutput:="22") + End Sub + + + + Public Sub Issue53593_3() + + Dim compilationDef = + + +Public Class C0 + + Public a, b As New C1((Function(n) n + 1)(1)) + + Shared Sub Main() + Dim x as New C0() + System.Console.Write(x.a.F1) + System.Console.Write(x.b.F1) + + x = New C0(True) + System.Console.Write(x.a.F1) + System.Console.Write(x.b.F1) + End Sub + + Sub New() + End Sub + + Sub New(b as Boolean) + End Sub +End Class + +Public Class C1 + + Public F1 as Integer + + Sub New(a As Integer) + F1 = a + End Sub +End Class + + + + Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40AndVBRuntime(compilationDef, TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, expectedOutput:="2222") + End Sub + + + + Public Sub Issue53593_4() + + Dim compilationDef = + + +Public Class C0 + + Public a, b As New C1((Function(n1) (Function(n2) n2 + 1)(n1))(1)) + + Shared Sub Main() + Dim x as New C0() + System.Console.Write(x.a.F1) + System.Console.Write(x.b.F1) + End Sub +End Class + +Public Class C1 + + Public F1 as Integer + + Sub New(a As Integer) + F1 = a + End Sub +End Class + + + + Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40AndVBRuntime(compilationDef, TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, expectedOutput:="22") + End Sub + + + + Public Sub Issue53593_5() + + Dim compilationDef = + + +Public Class C0 + + Shared Sub Main() + Test(Of Object)() + End Sub + + Shared Sub Test(Of T)() + Dim a, b As New C1((Function(n) + Dim x as T + x = Nothing + Return CObj(x) + n + 1 + End Function)(1)) + System.Console.Write(a.F1) + System.Console.Write(b.F1) + End Sub +End Class + +Public Class C1 + + Public F1 as Integer + + Sub New(a As Integer) + F1 = a + End Sub +End Class + + + + Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40AndVBRuntime(compilationDef, TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, expectedOutput:="22") + End Sub + + + + Public Sub Issue53593_6() + + Dim compilationDef = + + +Public Class C0 + + Public WithEvents a, b As New C1((Function(n) n + 1)(1)) + + Shared Sub Main() + Dim x as New C0() + System.Console.Write(x.a.F1) + System.Console.Write(x.b.F1) + End Sub +End Class + +Public Class C1 + + Public F1 as Integer + + Sub New(a As Integer) + F1 = a + End Sub +End Class + + + + Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40AndVBRuntime(compilationDef, TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, expectedOutput:="22") + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/UserDefinedBinaryOperators.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/UserDefinedBinaryOperators.vb index 496041616321f..7b7ddd5713b60 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/UserDefinedBinaryOperators.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/UserDefinedBinaryOperators.vb @@ -910,7 +910,7 @@ op_BitwiseOr End Sub - Public Sub OperatorMapping_BothSignedAndUnsignedShift() + Public Sub OperatorMapping_BothSignedAndUnsignedShift_01() Dim ilSource = > 2 << 3 + End Sub +End Module + ]]> + + + Dim compilation = CompilationUtils.CreateCompilationWithCustomILSource(compilationDef, ilSource.Value, includeVbRuntime:=True, options:=TestOptions.ReleaseExe) + + Dim verifier = CompileAndVerify(compilation, + expectedOutput:= + ) + End Sub + + + Public Sub OperatorMapping_BothSignedAndUnsignedShift_02() + + Dim ilSource = + + + Dim compilationDef = + + + Public Sub Does_Not_Enable_Incremental_Generators() + + Dim parseOptions = TestOptions.Regular + Dim compilation As Compilation = GetCompilation(parseOptions) + Dim testGenerator As VBIncrementalGenerator = New VBIncrementalGenerator() + Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(Of ISourceGenerator)(New IncrementalGeneratorWrapper(testGenerator)), parseOptions:=parseOptions) + + Dim outputCompilation As Compilation = Nothing + Dim outputDiagnostics As ImmutableArray(Of Diagnostic) = Nothing + driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, outputDiagnostics) + outputDiagnostics.Verify() + + Assert.Equal(1, outputCompilation.SyntaxTrees.Count()) + Assert.Equal(compilation, compilation) + Assert.False(testGenerator._initialized) + End Sub + + + Public Sub Does_Not_Prefer_Incremental_Generators() + + Dim parseOptions = TestOptions.Regular + Dim compilation As Compilation = GetCompilation(parseOptions) + Dim testGenerator As VBIncrementalAndSourceGenerator = New VBIncrementalAndSourceGenerator() + Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(Of ISourceGenerator)(testGenerator), parseOptions:=parseOptions) + + Dim outputCompilation As Compilation = Nothing + Dim outputDiagnostics As ImmutableArray(Of Diagnostic) = Nothing + driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, outputDiagnostics) + outputDiagnostics.Verify() + + Assert.Equal(1, outputCompilation.SyntaxTrees.Count()) + Assert.Equal(compilation, compilation) + Assert.False(testGenerator._initialized) + Assert.True(testGenerator._sourceInitialized) + Assert.True(testGenerator._sourceExecuted) + End Sub Shared Function GetCompilation(parseOptions As VisualBasicParseOptions, Optional source As String = "") As Compilation If (String.IsNullOrWhiteSpace(source)) Then @@ -348,5 +385,39 @@ End Class End Class + + Friend Class VBIncrementalGenerator + Implements IIncrementalGenerator + + Public _initialized As Boolean + + Public Sub Initialize(context As IncrementalGeneratorInitializationContext) Implements IIncrementalGenerator.Initialize + _initialized = True + End Sub + End Class + + + Friend Class VBIncrementalAndSourceGenerator + Implements IIncrementalGenerator + Implements ISourceGenerator + + Public _initialized As Boolean + Public _sourceInitialized As Boolean + Public _sourceExecuted As Boolean + + Public Sub Initialize(context As IncrementalGeneratorInitializationContext) Implements IIncrementalGenerator.Initialize + _initialized = True + End Sub + + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + _sourceInitialized = True + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + _sourceExecuted = True + End Sub + + + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingMethods.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingMethods.vb index 3003d9d737eb9..87e3287ef704a 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingMethods.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingMethods.vb @@ -965,5 +965,124 @@ BC30390: 'C2.Private Overloads Sub M2()' is not accessible in this context becau ]]>) End Sub + + + Public Sub TestAmbiguousImplementationMethod() + + Dim ilSource = + +{ + // Methods + .method public hidebysig abstract virtual + void Method ( + int32 i + ) cil managed + { + } // end of method Interface`2::Method + + .method public hidebysig abstract virtual + void Method ( + !T i + ) cil managed + { + } // end of method Interface`2::Method + + .method public hidebysig abstract virtual + void Method ( + !U i + ) cil managed + { + } // end of method Interface`2::Method + +} // end of class Interface`2 + +.class public auto ansi beforefieldinit Base`1 + extends [mscorlib]System.Object + implements class Interface`2 +{ + // Methods + .method public hidebysig newslot virtual + void Method ( + int32 i + ) cil managed + { + .override method instance void class Interface`2::Method(int32) + // Method begins at RVA 0x2050 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method Base`1::Method + + .method public hidebysig newslot virtual + void Method ( + !T i + ) cil managed + { + .override method instance void class Interface`2::Method(!0) + .override method instance void class Interface`2::Method(!1) + // Method begins at RVA 0x2050 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method Base`1::Method + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2053 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Base`1::.ctor + +} // end of class Base`1 +]]> + + Dim compilationDef = + + +Option Strict Off + +Imports System + +Module Module1 + Sub Main() + End Sub +End Module + + + + Dim compilation = CompilationUtils.CreateCompilationWithCustomILSource(compilationDef, ilSource.Value, includeVbRuntime:=True, options:=TestOptions.ReleaseExe) + + Dim b = compilation.GlobalNamespace.GetTypeMember("Base") + Dim bI = b.Interfaces().Single() + Dim biMethods = bI.GetMembers() + + Assert.Equal("Sub [Interface](Of T, U).Method(i As System.Int32)", biMethods(0).OriginalDefinition.ToTestDisplayString()) + Assert.Equal("Sub [Interface](Of T, U).Method(i As T)", biMethods(1).OriginalDefinition.ToTestDisplayString()) + Assert.Equal("Sub [Interface](Of T, U).Method(i As U)", biMethods(2).OriginalDefinition.ToTestDisplayString()) + + Dim bMethods = b.GetMembers() + + Assert.Equal("Sub Base(Of T).Method(i As System.Int32)", bMethods(0).ToTestDisplayString()) + Assert.Equal("Sub Base(Of T).Method(i As T)", bMethods(1).ToTestDisplayString()) + + Dim bM1Impl = DirectCast(bMethods(0), MethodSymbol).ExplicitInterfaceImplementations + Dim bM2Impl = DirectCast(bMethods(1), MethodSymbol).ExplicitInterfaceImplementations + Assert.Equal(biMethods(0), bM1Impl.Single()) + + Assert.Equal(2, bM2Impl.Length) + Assert.Equal(biMethods(1), bM2Impl(0)) + Assert.Equal(biMethods(2), bM2Impl(1)) + End Sub End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Retargeting/RetargetingTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Retargeting/RetargetingTests.vb index b4380d1e3bf67..b61ce12c60488 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Retargeting/RetargetingTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Retargeting/RetargetingTests.vb @@ -3255,6 +3255,40 @@ End Class Assert.True(DirectCast(cs, INamedTypeSymbol).IsSerializable) End Sub + + Public Sub ExplicitInterfaceImplementationRetargetingGenericType() + Dim source1 = " +Public Class C1(Of T) + Public Interface I1 + Sub M(x As T) + End Interface +End Class +" + Dim ref1 = CreateEmptyCompilation("").ToMetadataReference() + Dim compilation1 = CreateCompilation(source1, references:={ref1}) + + Dim source2 = " +Public Class C2(Of U) + Implements C1(Of U).I1 + + Sub M(x As U) Implements C1(Of U).I1.M + End Sub +End Class +" + Dim compilation2 = CreateCompilation(source2, references:={compilation1.ToMetadataReference(), ref1, CreateEmptyCompilation("").ToMetadataReference()}) + + Dim compilation3 = CreateCompilation("", references:={compilation1.ToMetadataReference(), compilation2.ToMetadataReference()}) + + Assert.NotSame(compilation2.GetTypeByMetadataName("C1`1"), compilation3.GetTypeByMetadataName("C1`1")) + + Dim c2 = compilation3.GetTypeByMetadataName("C2`1") + Assert.IsType(Of RetargetingNamedTypeSymbol)(c2) + + Dim m = c2.GetMethod("M") + + Assert.Equal(c2.Interfaces().Single().GetMethod("M"), m.ExplicitInterfaceImplementations.Single()) + End Sub + End Class #End If diff --git a/src/EditorFeatures/CSharp/CSharpEditorResources.resx b/src/EditorFeatures/CSharp/CSharpEditorResources.resx index 0498beacc31e0..3709e7f0ef1cb 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorResources.resx +++ b/src/EditorFeatures/CSharp/CSharpEditorResources.resx @@ -437,4 +437,7 @@ Outside namespace + + Prefer 'null' check over type check + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs index 5d42e7a200b3e..7cd49b0f0a6a5 100644 --- a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs @@ -223,6 +223,7 @@ private static bool MoveCaretToSemicolonPosition( { return MoveCaretToFinalPositionInStatement(speculative, currentNode, args, originalCaret, caret, true); } + return false; } else if (syntaxFacts.IsStatement(currentNode) diff --git a/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs b/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs index 41df9fab1d1dc..fa8faed093731 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs @@ -71,6 +71,7 @@ public PEFile Resolve(IAssemblyReference name) { Log(CSharpEditorResources.WARN_Version_mismatch_Expected_0_Got_1, name.Version, assemblies[0].Identity.Version); } + return MakePEFile(assemblies[0]); } diff --git a/src/EditorFeatures/CSharp/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs b/src/EditorFeatures/CSharp/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs index bce834f0c12ec..8e4de95a1439a 100644 --- a/src/EditorFeatures/CSharp/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs +++ b/src/EditorFeatures/CSharp/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs @@ -101,6 +101,10 @@ private static IEnumerable GetNullCheckingCodeStyleOptions(Ana description: CSharpEditorResources.Prefer_conditional_delegate_call, editorConfigOptions: editorConfigOptions, visualStudioOptions: visualStudioOptions, updater: updaterService); + yield return CodeStyleSetting.Create(option: CSharpCodeStyleOptions.PreferNullCheckOverTypeCheck, + description: CSharpEditorResources.Prefer_null_check_over_type_check, + editorConfigOptions: editorConfigOptions, + visualStudioOptions: visualStudioOptions, updater: updaterService); } private static IEnumerable GetModifierCodeStyleOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions, OptionUpdater updaterService) diff --git a/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/ReturnStatementHighlighter.cs b/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/ReturnStatementHighlighter.cs index 8ab7f7733c7a3..223cada0e0768 100644 --- a/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/ReturnStatementHighlighter.cs +++ b/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/ReturnStatementHighlighter.cs @@ -63,6 +63,7 @@ private void HighlightRelatedKeywords(SyntaxNode node, List spans) if (!child.AsNode().IsReturnableConstruct()) HighlightRelatedKeywords(child.AsNode(), spans); } + break; } } diff --git a/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/YieldStatementHighlighter.cs b/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/YieldStatementHighlighter.cs index 42f0e62caf917..dfffb011685b0 100644 --- a/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/YieldStatementHighlighter.cs +++ b/src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/YieldStatementHighlighter.cs @@ -69,6 +69,7 @@ private void HighlightRelatedKeywords(SyntaxNode node, List spans) HighlightRelatedKeywords(child.AsNode(), spans); } } + break; } } diff --git a/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs b/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs index 02518184e9a8c..c1b8330890e7a 100644 --- a/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs +++ b/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs @@ -43,7 +43,9 @@ protected override ImmutableArray GetMembers(IEnumerable SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration, SyntaxKind.EventDeclaration, - SyntaxKind.IndexerDeclaration)) + SyntaxKind.IndexerDeclaration, + SyntaxKind.OperatorDeclaration, + SyntaxKind.ConversionOperatorDeclaration)) { builder.Add(member); } @@ -69,6 +71,8 @@ protected override SyntaxToken GetDeclarationToken(SyntaxNode declarationNode) VariableDeclaratorSyntax variableDeclaratorNode => variableDeclaratorNode.Identifier, TypeDeclarationSyntax baseTypeDeclarationNode => baseTypeDeclarationNode.Identifier, IndexerDeclarationSyntax indexerDeclarationNode => indexerDeclarationNode.ThisKeyword, + OperatorDeclarationSyntax operatorDeclarationNode => operatorDeclarationNode.OperatorToken, + ConversionOperatorDeclarationSyntax conversionOperatorDeclarationNode => conversionOperatorDeclarationNode.Type.GetFirstToken(), // Shouldn't reach here since the input declaration nodes are coming from GetMembers() method above _ => throw ExceptionUtilities.UnexpectedValue(declarationNode), }; diff --git a/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj b/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj index 37cc1a409ee8d..71eb76a34f9e7 100644 --- a/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj @@ -6,7 +6,7 @@ Microsoft.CodeAnalysis.Editor.CSharp netcoreapp3.1;netstandard2.0 true - partial + partial true diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf index 08408a67dd409..9d05d7a21856f 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf @@ -372,6 +372,11 @@ Preferovat lokální funkci před anonymní funkcí + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Upřednostňovat porovnávání vzorů diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf index f602885cf3ed3..438c2f31e1494 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf @@ -372,6 +372,11 @@ Lokale Funktion gegenüber anonymer Funktion bevorzugen + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Musterabgleich bevorzugen diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf index cf02c7fb87f3d..5c28c9491507e 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf @@ -372,6 +372,11 @@ Preferir una función local frente a una función anónima + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferir coincidencia de patrones diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf index e407bf2c439e1..1973eec00c4e4 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf @@ -372,6 +372,11 @@ Préférer une fonction locale à une fonction anonyme + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Préférer les critères spéciaux diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf index 410040f6b23a0..2aa73552e0c36 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf @@ -372,6 +372,11 @@ Preferisci la funzione locale a quella anonima + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferisci i criteri di ricerca diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf index 2276f9274aa04..e64a9efb093c3 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf @@ -372,6 +372,11 @@ 匿名関数よりローカル関数を優先します + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching パターン マッチングを優先する diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf index 44dd19bfe303e..f9cf0e5c4d7e2 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf @@ -372,6 +372,11 @@ 익명 함수보다 로컬 함수 선호 + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching 패턴 일치 선호 diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf index 3dc3b375fdf0d..486b71d2a215c 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf @@ -372,6 +372,11 @@ Preferuj funkcję lokalną zamiast funkcji anonimowej + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferuj dopasowywanie do wzorca diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf index 9003529dc846b..278d4c584974c 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf @@ -372,6 +372,11 @@ Preferir usar função anônima em vez de local + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferir a correspondência de padrões diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf index faa1c9676dd99..51902f502a6c2 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf @@ -372,6 +372,11 @@ Предпочитать локальную функцию анонимной функции + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Предпочитать соответствие шаблону diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf index c41985167cadc..6ff776740d16b 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf @@ -372,6 +372,11 @@ Anonim işlevler yerine yerel işlevleri tercih et + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Desen eşleştirmeyi tercih et diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf index cea90185fea94..57f7b1c1bca0b 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf @@ -372,6 +372,11 @@ 首选本地函数而不是匿名函数 + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching 首选模式匹配 diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf index 13988058c5938..4b7d00fbb0ef3 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf @@ -372,6 +372,11 @@ 使用區域函式優先於匿名函式 + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching 建議使用模式比對 diff --git a/src/EditorFeatures/CSharpTest/AddParameter/AddParameterTests.cs b/src/EditorFeatures/CSharpTest/AddParameter/AddParameterTests.cs index 3f5f22412b791..d3961df775dbc 100644 --- a/src/EditorFeatures/CSharpTest/AddParameter/AddParameterTests.cs +++ b/src/EditorFeatures/CSharpTest/AddParameter/AddParameterTests.cs @@ -2849,5 +2849,59 @@ void Test() } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddParameter)] + [WorkItem(54408, "https://github.com/dotnet/roslyn/issues/54408")] + public async Task TestPositionalRecord() + { + await TestInRegularAndScriptAsync(@" +var b = ""B""; +var r = [|new R(1, b)|]; + +record R(int A); + +namespace System.Runtime.CompilerServices +{ + public static class IsExternalInit { } +} +", @" +var b = ""B""; +var r = new R(1, b); + +record R(int A, string b); + +namespace System.Runtime.CompilerServices +{ + public static class IsExternalInit { } +} +", parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp9)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddParameter)] + [WorkItem(54408, "https://github.com/dotnet/roslyn/issues/54408")] + public async Task TestPositionalRecordStruct() + { + await TestInRegularAndScriptAsync(@" +var b = ""B""; +var r = [|new R(1, b)|]; + +record struct R(int A); + +namespace System.Runtime.CompilerServices +{ + public static class IsExternalInit { } +} +", @" +var b = ""B""; +var r = new R(1, b); + +record struct R(int A, string b); + +namespace System.Runtime.CompilerServices +{ + public static class IsExternalInit { } +} +", parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp9)); + } } } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/AddAwait/AddAwaitTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/AddAwait/AddAwaitTests.cs index 8a7547abf1828..977226441d9ac 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/AddAwait/AddAwaitTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/AddAwait/AddAwaitTests.cs @@ -1448,5 +1448,21 @@ async Task A() return await (null as Task); } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddAwait)] + [WorkItem(1345322, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1345322")] + public async Task TestOnTaskTypeItself() + { + await TestMissingAsync( +@"using System.Threading.Tasks; + +class Program +{ + static async [||]Task Main(string[] args) + { + } +} +"); + } } } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs index fea96d9c25894..df4d41e43624a 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs @@ -1775,5 +1775,50 @@ void M1() }"; await TestInRegularAndScriptAsync(code, expected, index: 0); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestHighlightReturnType() + { + var code = +@"using System; +class TestClass +{ + [|int|] M(int x) + { + return x; + } + + void M1() + { + M(5); + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestTypeOfOnString() + { + var code = +@"using System; +class TestClass +{ + void M() + { + var x = [|typeof(string);|] + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(Type x) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } } } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs b/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs index 2795b666bef30..c3a43618a02c9 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs @@ -1408,5 +1408,51 @@ record CacheContext(String Message); await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText); } + + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveType)] + public async Task MoveClassInTopLevelStatements() + { + var code = @" +using ConsoleApp1; +using System; + +var c = new C(); +Console.WriteLine(c.Hello); + +class [||]C +{ + public string Hello => ""Hello""; +}"; + + var codeAfterMove = @" +using ConsoleApp1; +using System; + +var c = new C(); +Console.WriteLine(c.Hello); +"; + + var expectedDocumentName = "C.cs"; + var destinationDocumentText = @"class C +{ + public string Hello => ""Hello""; +}"; + + await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveType)] + public async Task MissingInTopLevelStatementsOnly() + { + var code = @" +using ConsoleApp1; +using System; + +var c = new object(); +[||]Console.WriteLine(c.ToString()); +"; + + await TestMissingAsync(code); + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs index f25ebc09047f0..8caf3f8b9b40d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs @@ -1651,6 +1651,43 @@ public static void Main() } } } +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionNullForgivingOperatorHandling() + { + await VerifyCustomCommitProviderAsync(@" +#nullable enable + +public class C { + public static explicit operator int(C c) => 0; +} + +public class Program +{ + public static void Main() + { + C? c = null; + var i = c!.$$ + } +} +", "int", @" +#nullable enable + +public class C { + public static explicit operator int(C c) => 0; +} + +public class Program +{ + public static void Main() + { + C? c = null; + var i = ((int)c!)$$ + } +} "); } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs index 9af45df20124e..8fd6906bd669d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs @@ -498,5 +498,44 @@ await VerifyItemInEditorBrowsableContextsAsync( sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerNullForgivingOperatorHandling() + { + await VerifyCustomCommitProviderAsync(@" +#nullable enable + +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + C? c = null; + var i = c!.$$ + } +} +", "this", @" +#nullable enable + +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + C? c = null; + var i = c![$$] + } +} +"); + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs index 12165d91daa90..6776e7693a4f0 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs @@ -832,5 +832,44 @@ await VerifyItemInEditorBrowsableContextsAsync( referencedLanguage: LanguageNames.CSharp, hideAdvancedMembers: true); } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorBinaryNullForgivingHandling() + { + await VerifyCustomCommitProviderAsync(@" +#nullable enable + +public class C +{ + public static C operator +(C a, C b) => default; +} + +public class Program +{ + public static void Main() + { + C? c = null; + var _ = c!.$$ + } +} +", "+", @" +#nullable enable + +public class C +{ + public static C operator +(C a, C b) => default; +} + +public class Program +{ + public static void Main() + { + C? c = null; + var _ = c! + $$ + } +} +"); + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SnippetCompletionProviderTests.cs index 339f971c62b2a..31cbca9838720 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SnippetCompletionProviderTests.cs @@ -78,6 +78,10 @@ public async Task SnippetsNotInPreProcessorContextDirectiveNameAlreadyTyped() public async Task ShowRegionSnippetWithHashRTyped() => await VerifyItemExistsAsync(@"#r$$", MockSnippetInfoService.PreProcessorSnippetShortcut.Substring(1), sourceCodeKind: SourceCodeKind.Regular); + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task SnippetsInLineSpanDirective() + => await VerifyItemIsAbsentAsync(@"#line (1, 2) - (3, 4) $$", MockSnippetInfoService.PreProcessorSnippetShortcut, sourceCodeKind: SourceCodeKind.Regular); + [WorkItem(968256, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/968256")] [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task ShowSnippetsFromOtherContext() diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index 01a35d5e86898..ae3decc2ed975 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -8070,6 +8070,40 @@ public void goo() await VerifyItemIsAbsentAsync(markup, "value"); } + [WorkItem(54361, "https://github.com/dotnet/roslyn/issues/54361")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task ConditionalAccessNullableIsUnwrappedOnParameter() + { + var markup = @" +class A +{ + void M(System.DateTime? dt) + { + dt?.$$ + } +} +"; + await VerifyItemExistsAsync(markup, "Day"); + await VerifyItemIsAbsentAsync(markup, "Value"); + } + + [WorkItem(54361, "https://github.com/dotnet/roslyn/issues/54361")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NullableIsNotUnwrappedOnParameter() + { + var markup = @" +class A +{ + void M(System.DateTime? dt) + { + dt.$$ + } +} +"; + await VerifyItemExistsAsync(markup, "Value"); + await VerifyItemIsAbsentAsync(markup, "Day"); + } + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task CompletionAfterConditionalIndexing() { @@ -9576,6 +9610,7 @@ void goo() { await VerifyItemExistsAsync(markup, "Item" + i); } + await VerifyItemExistsAsync(markup, "ToString"); await VerifyItemIsAbsentAsync(markup, "Item1"); @@ -11293,5 +11328,38 @@ int M() } }", "y"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(53930, "https://github.com/dotnet/roslyn/issues/53930")] + public async Task TestTypeParameterConstraintedToInterfaceWithStatics() + { + var source = @" +interface I1 +{ + static void M0(); + static abstract void M1(); + abstract static int P1 { get; set; } + abstract static event System.Action E1; +} + +interface I2 +{ + static abstract void M2(); +} + +class Test +{ + void M(T x) where T : I1, I2 + { + T.$$ + } +} +"; + await VerifyItemIsAbsentAsync(source, "M0"); + await VerifyItemExistsAsync(source, "M1"); + await VerifyItemExistsAsync(source, "M2"); + await VerifyItemExistsAsync(source, "P1"); + await VerifyItemExistsAsync(source, "E1"); + } } } diff --git a/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs b/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs index 2116a5ac38b23..16f228209a291 100644 --- a/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs @@ -48,7 +48,9 @@ private static async Task TestAsync( if (index != 0) Assert.NotNull(equivalenceKey); - var test = new VerifyCS.Test + options ??= new OptionsCollection(LanguageNames.CSharp); + + await new VerifyCS.Test { TestCode = text, FixedCode = expected, @@ -57,11 +59,8 @@ private static async Task TestAsync( CodeActionIndex = index, CodeActionEquivalenceKey = equivalenceKey, ExactActionSetOffered = actions, - }; - - if (options != null) - test.Options.AddRange(options); - await test.RunAsync(); + Options = { options }, + }.RunAsync(); } #region update containing member tests @@ -2278,7 +2277,8 @@ public static implicit operator NewStruct((int a, int a) value) return new NewStruct(value.a, value.a); } }"; - var test = new VerifyCS.Test + + await new VerifyCS.Test { TestCode = text, FixedCode = expected, @@ -2349,11 +2349,9 @@ public static implicit operator NewStruct((int a, int a) value) // /0/Test0.cs(49,45): error CS0229: Ambiguity between '(int a, int a).a' and '(int a, int a).a' DiagnosticResult.CompilerError("CS0229").WithSpan(49, 45, 49, 46).WithArguments("(int a, int a).a", "(int a, int a).a"), } - } - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + }, + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -3352,7 +3350,7 @@ void Goo() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestState = { @@ -3373,10 +3371,8 @@ void Goo() CodeActionIndex = 1, CodeActionEquivalenceKey = Scope.ContainingType.ToString(), TestHost = host, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } #endregion update containing project tests @@ -3511,7 +3507,7 @@ void Goo() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { CodeActionIndex = 2, CodeActionEquivalenceKey = Scope.ContainingProject.ToString(), @@ -3524,10 +3520,8 @@ void Goo() { Sources = { expected1, expected2 }, }, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } #endregion @@ -3638,7 +3632,7 @@ void Goo() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { CodeActionIndex = 3, CodeActionEquivalenceKey = Scope.DependentProjects.ToString(), @@ -3667,10 +3661,8 @@ void Goo() } }, }, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -3777,7 +3769,7 @@ void Goo() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { CodeActionIndex = 3, CodeActionEquivalenceKey = Scope.DependentProjects.ToString(), @@ -3798,10 +3790,8 @@ void Goo() ["DependencyProject"] = { Sources = { expected2 } } }, }, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } #endregion diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs index 76b210500ddf9..c959df4927171 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs @@ -2522,6 +2522,7 @@ public Test(string s, Base b, int i, params object[] list) : this(d : [||]b, s : var (actions, actionToInvoke) = await GetCodeActionsAsync(workspace, new TestParameters()); Assert.Equal(2, actions.Length); } + var expect_0 = @" class Program @@ -2671,6 +2672,7 @@ void M() var (actions, actionToInvoke) = await GetCodeActionsAsync(workspace, new TestParameters()); Assert.Equal(2, actions.Length); } + var expect_0 = @" class Program @@ -2746,6 +2748,7 @@ void M() var (actions, actionToInvoke) = await GetCodeActionsAsync(workspace, new TestParameters()); Assert.Equal(3, actions.Length); } + var expect_0 = @" class Program diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index de59148fc6de0..0fc6908c7a5b2 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -927,6 +927,7 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, trivia.GetLocation())); } + break; } } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs index 53caa05b1a1f1..ef4139fe6426c 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs @@ -1001,7 +1001,7 @@ interface I2 static T1 M1([|T1|] x) => x; } ", - expected: LanguageVersion.Preview, + expected: LanguageVersion.CSharp9, new CSharpParseOptions(LanguageVersion.CSharp8)); } @@ -1011,13 +1011,13 @@ public async Task UpgradeProjectForSealedToStringInRecords_CS8912() await TestLanguageVersionUpgradedAsync( @" - + Assembly2 record [|Derived|] : Base; - + public record Base { @@ -1027,7 +1027,48 @@ public record Base ", - expected: LanguageVersion.Preview, + expected: LanguageVersion.CSharp10, + new CSharpParseOptions(LanguageVersion.CSharp9)); + } + + [Fact] + public async Task UpgradeProjectForTargetTypedNew() + { + await TestLanguageVersionUpgradedAsync(@" +class Test +{ + Test t = [|new()|]; +}", + LanguageVersion.CSharp9, + new CSharpParseOptions(LanguageVersion.CSharp8)); + } + + [Fact] + public async Task UpgradeProjectForGlobalUsing() + { + await TestLanguageVersionUpgradedAsync(@" +[|global using System;|] +", + LanguageVersion.CSharp10, + new CSharpParseOptions(LanguageVersion.CSharp9)); + } + + [Fact] + public async Task UpgradeProjectForImplicitImplementationOfNonPublicMemebers_CS8704() + { + await TestLanguageVersionUpgradedAsync( +@" +public interface I1 +{ + protected void M01(); +} + +class C1 : [|I1|] +{ + public void M01() {} +} +", + expected: LanguageVersion.CSharp10, new CSharpParseOptions(LanguageVersion.CSharp9)); } } diff --git a/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs b/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs index 326a0fc2cdb94..80b636ce7a8ed 100644 --- a/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs +++ b/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs @@ -54,6 +54,22 @@ record R;"; VerifyTypingCharacter(code, expected); } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void TypingCharacter_RecordStruct() + { + var code = +@"//$$ +record struct R;"; + + var expected = +@"/// +/// $$ +/// +record struct R;"; + + VerifyTypingCharacter(code, expected); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] public void TypingCharacter_RecordWithPositionalParameters() { @@ -72,6 +88,24 @@ record R(string S, int I);"; VerifyTypingCharacter(code, expected); } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void TypingCharacter_RecordStructWithPositionalParameters() + { + var code = +@"//$$ +record struct R(string S, int I);"; + + var expected = +@"/// +/// $$ +/// +/// +/// +record struct R(string S, int I);"; + + VerifyTypingCharacter(code, expected); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] public void TypingCharacter_Class_NewLine() { @@ -1515,6 +1549,20 @@ record R;"; VerifyInsertCommentCommand(code, expected); } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void Command_RecordStruct() + { + var code = "record struct R$$;"; + + var expected = +@"/// +/// $$ +/// +record struct R;"; + + VerifyInsertCommentCommand(code, expected); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] public void Command_RecordWithPositionalParameters() { @@ -1531,6 +1579,22 @@ record R(string S, int I);"; VerifyInsertCommentCommand(code, expected); } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void Command_RecordStructWithPositionalParameters() + { + var code = "record struct R$$(string S, int I);"; + + var expected = +@"/// +/// $$ +/// +/// +/// +record struct R(string S, int I);"; + + VerifyInsertCommentCommand(code, expected); + } + [WorkItem(4817, "https://github.com/dotnet/roslyn/issues/4817")] [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] public void Command_Class_AutoGenerateXmlDocCommentsOff() diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 4fa5b89fd2ade..a5306fe2a6398 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -1867,7 +1867,7 @@ public C() {} var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "const int a = 1", FeaturesResources.const_field)); + Diagnostic(RudeEditKind.ModifiersUpdate, "a = 1", FeaturesResources.const_field)); } [Fact] @@ -1917,7 +1917,7 @@ public C() {} var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "const int a = 1, b = 2", FeaturesResources.const_field)); + Diagnostic(RudeEditKind.ModifiersUpdate, "a = 1", FeaturesResources.const_field)); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs index 4b417edbe5a0a..9e69b9b3ad552 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index fd1d4962d4c04..794c9d3f61a9e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests @@ -34,6 +35,17 @@ internal enum MethodKind ConstructorWithParameters } + public static string GetResource(string keyword) + => keyword switch + { + "class" => FeaturesResources.class_, + "struct" => CSharpFeaturesResources.struct_, + "interface" => FeaturesResources.interface_, + "record" => CSharpFeaturesResources.record_, + "record struct" => CSharpFeaturesResources.record_struct, + _ => throw ExceptionUtilities.UnexpectedValue(keyword) + }; + internal static SemanticEditDescription[] NoSemanticEdits = Array.Empty(); internal static RudeEditDiagnosticDescription Diagnostic(RudeEditKind rudeEditKind, string squiggle, params string[] arguments) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 095a59d3feecc..3827ac1e2d9b2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -428,7 +428,7 @@ public void ExternAliasDelete() #endregion - #region Attributes + #region Assembly/Module Attributes [Fact] public void Insert_TopLevelAttribute() @@ -492,243 +492,157 @@ public void Reorder_TopLevelAttribute() edits.VerifyRudeDiagnostics(); } - [Fact] - public void UpdateAttributes1() - { - var attribute = "public class A1Attribute : System.Attribute { }\n\n" + - "public class A2Attribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A1]class C { }"; - var src2 = attribute + "[A2]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A1]class C { }]@98 -> [[A2]class C { }]@98"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } - - [Fact] - public void UpdateAttributes2() - { - var src1 = "[System.Obsolete(\"1\")]class C { }"; - var src2 = "[System.Obsolete(\"2\")]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[System.Obsolete(\"1\")]class C { }]@0 -> [[System.Obsolete(\"2\")]class C { }]@0"); + #endregion - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } + #region Types - [Fact] - public void DeleteAttributes() + [Theory] + [InlineData("class", "struct")] + [InlineData("class", "record")] // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 + [InlineData("class", "record struct")] + [InlineData("struct", "record struct")] // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 + public void Type_Kind_Update(string oldKeyword, string newKeyword) { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A, B]class C { }"; - var src2 = attribute + "[A]class C { }"; + var src1 = oldKeyword + " C { }"; + var src2 = newKeyword + " C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [[A, B]class C { }]@96 -> [[A]class C { }]@96"); + "Update [" + oldKeyword + " C { }]@0 -> [" + newKeyword + " C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.TypeKindUpdate, newKeyword + " C")); } [Fact] - public void DeleteAttributes2() + public void Type_Modifiers_Static_Remove() { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[B, A]class C { }"; - var src2 = attribute + "[A]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[B, A]class C { }]@96 -> [[A]class C { }]@96"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } - - [Fact] - public void InsertAttributes_SupportedByRuntime() - { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A]class C { }"; - var src2 = attribute + "[A, B]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); - - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C")) }, - capabilities: EditAndContinueTestHelpers.Net5RuntimeCapabilities | EditAndContinueCapabilities.ChangeCustomAttributes); - } - - [Fact] - public void InsertAttributes1() - { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A]class C { }"; - var src2 = attribute + "[A, B]class C { }"; + var src1 = "public static class C { }"; + var src2 = "public class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + "Update [public static class C { }]@0 -> [public class C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.ModifiersUpdate, "public class C", FeaturesResources.class_)); } - [Fact] - public void InsertAttributes2() + [Theory] + [InlineData("public")] + [InlineData("protected")] + [InlineData("private")] + [InlineData("private protected")] + [InlineData("internal protected")] + public void Type_Modifiers_Accessibility_Change(string accessibility) { - var attribute = "public class AAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "class C { }"; - var src2 = attribute + "[A]class C { }"; + var src1 = accessibility + " class C { }"; + var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [class C { }]@48 -> [[A]class C { }]@48"); + "Update [" + accessibility + " class C { }]@0 -> [class C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } - - [Fact] - public void ReorderAttributes1() - { - var src1 = "[A(1), B(2), C(3)]class C { }"; - var src2 = "[C(3), A(1), B(2)]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A(1), B(2), C(3)]class C { }]@0 -> [[C(3), A(1), B(2)]class C { }]@0"); - - edits.VerifyRudeDiagnostics(); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", FeaturesResources.class_)); } - [Fact] - public void ReorderAttributes2() - { - var src1 = "[A, B, C]class C { }"; - var src2 = "[B, C, A]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A, B, C]class C { }]@0 -> [[B, C, A]class C { }]@0"); + [Theory] + [InlineData("public", "public")] + [InlineData("internal", "internal")] + [InlineData("", "internal")] + [InlineData("internal", "")] + [InlineData("protected", "protected")] + [InlineData("private", "private")] + [InlineData("private protected", "private protected")] + [InlineData("internal protected", "internal protected")] + public void Type_Modifiers_Accessibility_Partial(string accessibilityA, string accessibilityB) + { + var srcA1 = accessibilityA + " partial class C { }"; + var srcB1 = "partial class C { }"; + var srcA2 = "partial class C { }"; + var srcB2 = accessibilityB + " partial class C { }"; - edits.VerifyRudeDiagnostics(); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(), + }); } [Fact] - public void ReorderAndUpdateAttributes() + public void Type_Modifiers_Internal_Remove() { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[System.Obsolete(\"1\"), A, B]class C { }"; - var src2 = attribute + "[A, B, System.Obsolete(\"2\")]class C { }"; + var src1 = "internal interface C { }"; + var src2 = "interface C { }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[System.Obsolete(\"1\"), A, B]class C { }]@96 -> [[A, B, System.Obsolete(\"2\")]class C { }]@96"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + edits.VerifySemantics(); } - #endregion - - #region Classes, Structs, Interfaces - [Fact] - public void TypeKindUpdate() + public void Type_Modifiers_Internal_Add() { - var src1 = "class C { }"; - var src2 = "struct C { }"; + var src1 = "struct C { }"; + var src2 = "internal struct C { }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [class C { }]@0 -> [struct C { }]@0"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "struct C", CSharpFeaturesResources.struct_)); + edits.VerifySemantics(); } - [Fact] - public void TypeKindUpdate2() + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("record struct")] + public void Type_Modifiers_NestedPrivateInInterface_Remove(string keyword) { - // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 - var src1 = "class C { }"; - var src2 = "record C { }"; + var src1 = "interface C { private " + keyword + " S { } }"; + var src2 = "interface C { " + keyword + " S { } }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [class C { }]@0 -> [record C { }]@0"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.ChangingAccessibility, keyword + " S", GetResource(keyword))); } - [Fact] - public void TypeKindUpdate3() + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("record struct")] + public void Type_Modifiers_NestedPrivateInClass_Add(string keyword) { - var src1 = "record C { }"; - var src2 = "record struct C { }"; + var src1 = "class C { " + keyword + " S { } }"; + var src2 = "class C { private " + keyword + " S { } }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [record C { }]@0 -> [record struct C { }]@0"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "record struct C", CSharpFeaturesResources.record_struct)); + edits.VerifySemantics(); } - [Fact] - public void Class_Modifiers_Update() + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("record struct")] + public void Type_Modifiers_NestedPublicInInterface_Add(string keyword) { - var src1 = "public static class C { }"; - var src2 = "public class C { }"; + var src1 = "interface C { " + keyword + " S { } }"; + var src2 = "interface C { public " + keyword + " S { } }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [public static class C { }]@0 -> [public class C { }]@0"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public class C", FeaturesResources.class_)); + edits.VerifySemantics(); } [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] - public void Class_ModifiersUpdate_IgnoreUnsafe() + public void Type_Modifiers_Unsafe_Add() { var src1 = "public class C { }"; var src2 = "public unsafe class C { }"; @@ -742,7 +656,7 @@ public void Class_ModifiersUpdate_IgnoreUnsafe() } [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] - public void ModifiersUpdate_IgnoreUnsafe() + public void Type_Modifiers_Unsafe_Remove() { var src1 = @" using System; @@ -789,7 +703,7 @@ public event Action A { add { } remove { } } } [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] - public void ModifiersUpdate_IgnoreUnsafe2() + public void Type_Modifiers_Unsafe_DeleteInsert() { var srcA1 = "partial class C { unsafe void F() { } }"; var srcB1 = "partial class C { }"; @@ -809,7 +723,7 @@ public void ModifiersUpdate_IgnoreUnsafe2() } [Fact] - public void Struct_Modifiers_Ref_Update1() + public void Type_Modifiers_Ref_Add() { var src1 = "public struct C { }"; var src2 = "public ref struct C { }"; @@ -824,7 +738,7 @@ public void Struct_Modifiers_Ref_Update1() } [Fact] - public void Struct_Modifiers_Ref_Update2() + public void Type_Modifiers_Ref_Remove() { var src1 = "public ref struct C { }"; var src2 = "public struct C { }"; @@ -839,7 +753,7 @@ public void Struct_Modifiers_Ref_Update2() } [Fact] - public void Struct_Modifiers_Readonly_Update1() + public void Type_Modifiers_ReadOnly_Add() { var src1 = "public struct C { }"; var src2 = "public readonly struct C { }"; @@ -854,7 +768,7 @@ public void Struct_Modifiers_Readonly_Update1() } [Fact] - public void Struct_Modifiers_Readonly_Update2() + public void Type_Modifiers_ReadOnly_Remove() { var src1 = "public readonly struct C { }"; var src2 = "public struct C { }"; @@ -869,49 +783,175 @@ public void Struct_Modifiers_Readonly_Update2() } [Fact] - public void Interface_Modifiers_Update() + public void Type_Attribute_Update_NotSupportedByRuntime1() { - var src1 = "public interface C { }"; - var src2 = "interface C { }"; + var attribute = "public class A1Attribute : System.Attribute { }\n\n" + + "public class A2Attribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A1]class C { }"; + var src2 = attribute + "[A2]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [public interface C { }]@0 -> [interface C { }]@0"); + "Update [[A1]class C { }]@98 -> [[A2]class C { }]@98"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "interface C", FeaturesResources.interface_)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); } [Fact] - public void Struct_Modifiers_Update() + public void Type_Attribute_Update_NotSupportedByRuntime2() { - var src1 = "struct C { }"; - var src2 = "public struct C { }"; + var src1 = "[System.Obsolete(\"1\")]class C { }"; + var src2 = "[System.Obsolete(\"2\")]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [struct C { }]@0 -> [public struct C { }]@0"); + "Update [[System.Obsolete(\"1\")]class C { }]@0 -> [[System.Obsolete(\"2\")]class C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public struct C", CSharpFeaturesResources.struct_)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); } [Fact] - public void Struct_UnsafeModifier_Update() + public void Type_Attribute_Delete_NotSupportedByRuntime1() { - var src1 = "unsafe struct C { }"; - var src2 = "struct C { }"; + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A, B]class C { }"; + var src2 = attribute + "[A]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A, B]class C { }]@96 -> [[A]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Delete_NotSupportedByRuntime2() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[B, A]class C { }"; + var src2 = attribute + "[A]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [unsafe struct C { }]@0 -> [struct C { }]@0"); + "Update [[B, A]class C { }]@96 -> [[A]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Add() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A]class C { }"; + var src2 = attribute + "[A, B]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C")) }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + + [Fact] + public void Type_Attribute_Add_NotSupportedByRuntime1() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A]class C { }"; + var src2 = attribute + "[A, B]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Add_NotSupportedByRuntime2() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "class C { }"; + var src2 = attribute + "[A]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [class C { }]@48 -> [[A]class C { }]@48"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Reorder1() + { + var src1 = "[A(1), B(2), C(3)]class C { }"; + var src2 = "[C(3), A(1), B(2)]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A(1), B(2), C(3)]class C { }]@0 -> [[C(3), A(1), B(2)]class C { }]@0"); edits.VerifyRudeDiagnostics(); } + [Fact] + public void Type_Attribute_Reorder2() + { + var src1 = "[A, B, C]class C { }"; + var src2 = "[B, C, A]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A, B, C]class C { }]@0 -> [[B, C, A]class C { }]@0"); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Type_Attribute_ReorderAndUpdate_NotSupportedByRuntime() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[System.Obsolete(\"1\"), A, B]class C { }"; + var src2 = attribute + "[A, B, System.Obsolete(\"2\")]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[System.Obsolete(\"1\"), A, B]class C { }]@96 -> [[A, B, System.Obsolete(\"2\")]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + [Fact] public void Class_Name_Update1() { @@ -1220,7 +1260,7 @@ public void RefStructInsert() } [Fact] - public void ReadOnlyStructInsert() + public void Struct_ReadOnly_Insert() { var src1 = ""; var src2 = "readonly struct X { }"; @@ -1234,7 +1274,7 @@ public void ReadOnlyStructInsert() } [Fact] - public void RefStructUpdate() + public void Struct_RefModifier_Add() { var src1 = "struct X { }"; var src2 = "ref struct X { }"; @@ -1249,7 +1289,7 @@ public void RefStructUpdate() } [Fact] - public void ReadOnlyStructUpdate() + public void Struct_ReadonlyModifier_Add() { var src1 = "struct X { }"; var src2 = "readonly struct X { }"; @@ -1263,6 +1303,25 @@ public void ReadOnlyStructUpdate() Diagnostic(RudeEditKind.ModifiersUpdate, "readonly struct X", SyntaxFacts.GetText(SyntaxKind.StructKeyword))); } + [Theory] + [InlineData("ref")] + [InlineData("readonly")] + public void Struct_Modifiers_Partial_InsertDelete(string modifier) + { + var srcA1 = modifier + " partial struct S { }"; + var srcB1 = "partial struct S { }"; + var srcA2 = "partial struct S { }"; + var srcB2 = modifier + " partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults() + }); + } + [Fact] public void Class_ImplementingInterface_Add() { @@ -1626,7 +1685,7 @@ interface I { void F() {} } } [Fact] - public void Type_DeleteInsert_NonInsertableMembers() + public void Type_NonInsertableMembers_DeleteInsert() { var srcA1 = @" abstract class C @@ -1648,6 +1707,7 @@ void F() {} var srcA2 = srcB1; var srcB2 = srcA1; + // TODO: The methods without bodies do not need to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] @@ -1657,14 +1717,72 @@ void F() {} DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("AbstractMethod")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("G")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), }) }); } + [Fact] + public void Type_Attribute_NonInsertableMembers_DeleteInsert() + { + var srcA1 = @" +abstract class C +{ + public abstract void AbstractMethod(); + public virtual void VirtualMethod() {} + public override string ToString() => null; + public void I.G() {} +} + +interface I +{ + void G(); + void F() {} +} +"; + var srcB1 = ""; + + var srcA2 = ""; + var srcB2 = @" +abstract class C +{ + [System.Obsolete]public abstract void AbstractMethod(); + public virtual void VirtualMethod() {} + public override string ToString() => null; + public void I.G() {} +} + +interface I +{ + [System.Obsolete]void G(); + void F() {} +}"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("AbstractMethod")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + }) + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + [Fact] public void Type_DeleteInsert_DataMembers() { @@ -1698,6 +1816,8 @@ class C DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").SetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), }) }); @@ -1738,6 +1858,8 @@ partial class C DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").SetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), }) }); @@ -1778,6 +1900,8 @@ class C DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").SetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), }), @@ -2001,7 +2125,7 @@ public void Record_ImplementSynthesized_PrintMembers() var src2 = @" record C { - protected bool PrintMembers(System.Text.StringBuilder builder) + protected virtual bool PrintMembers(System.Text.StringBuilder builder) { return true; } @@ -2715,24 +2839,36 @@ public int X [Fact] public void Record_MoveProperty_Partial() { - var srcA1 = @"partial record C(int X) + var srcA1 = @" +partial record C(int X) { public int Y { get; init; } }"; - var srcB1 = @"partial record C;"; - var srcA2 = @"partial record C(int X);"; - var srcB2 = @"partial record C + var srcB1 = @" +partial record C; +"; + + var srcA2 = @" +partial record C(int X); +"; + + var srcB2 = @" +partial record C { public int Y { get; init; } }"; EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - - DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y").SetMethod) + }), }); } @@ -2976,7 +3112,7 @@ public void EnumBaseTypeDelete() } [Fact] - public void EnumModifierUpdate() + public void EnumAccessibilityChange() { var src1 = "public enum Color { Red = 1, Blue = 2, }"; var src2 = "enum Color { Red = 1, Blue = 2, }"; @@ -2986,8 +3122,18 @@ public void EnumModifierUpdate() edits.VerifyEdits( "Update [public enum Color { Red = 1, Blue = 2, }]@0 -> [enum Color { Red = 1, Blue = 2, }]@0"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "enum Color", FeaturesResources.enum_)); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAccessibility, "enum Color", FeaturesResources.enum_)); + } + + [Fact] + public void EnumAccessibilityNoChange() + { + var src1 = "internal enum Color { Red = 1, Blue = 2, }"; + var src2 = "enum Color { Red = 1, Blue = 2, }"; + + var edits = GetTopEdits(src1, src2); + edits.VerifySemantics(); } [Fact] @@ -3318,7 +3464,7 @@ public void Delegates_Rename() } [Fact] - public void Delegates_Update_Modifiers() + public void Delegates_Accessibility_Update() { var src1 = "public delegate void D();"; var src2 = "private delegate void D();"; @@ -3329,7 +3475,7 @@ public void Delegates_Update_Modifiers() "Update [public delegate void D();]@0 -> [private delegate void D();]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "private delegate void D()", FeaturesResources.delegate_)); + Diagnostic(RudeEditKind.ChangingAccessibility, "private delegate void D()", FeaturesResources.delegate_)); } [Fact] @@ -4535,101 +4681,6 @@ public void NamespaceReorder2() #region Members - [Fact] - public void MemberUpdate_Modifier_ReadOnly_Remove() - { - var src1 = @" -using System; - -struct S -{ - // methods - public readonly int M() => 1; - - // properties - public readonly int P => 1; - public readonly int Q { get; } - public int R { readonly get; readonly set; } - - // events - public readonly event Action E { add {} remove {} } - public event Action F { readonly add {} readonly remove {} } -}"; - var src2 = @" -using System; -struct S -{ - // methods - public int M() => 1; - - // properties - public int P => 1; - public int Q { get; } - public int R { get; set; } - - // events - public event Action E { add {} remove {} } - public event Action F { add {} remove {} } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public int M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ModifiersUpdate, "public int P", FeaturesResources.property_), - Diagnostic(RudeEditKind.ModifiersUpdate, "public int Q", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "add", FeaturesResources.event_accessor), - Diagnostic(RudeEditKind.ModifiersUpdate, "remove", FeaturesResources.event_accessor)); - } - - [Fact] - public void MemberUpdate_Modifier_ReadOnly_Add() - { - var src1 = @" -using System; - -struct S -{ - // methods - public int M() => 1; - - // properties - public int P => 1; - public int Q { get; } - public int R { get; set; } - - // events - public event Action E { add {} remove {} } - public event Action F { add {} remove {} } -}"; - var src2 = @" -using System; - -struct S -{ - // methods - public readonly int M() => 1; - - // properties - public readonly int P => 1; - public readonly int Q { get; } - public int R { readonly get; readonly set; } - - // events - public readonly event Action E { add {} remove {} } - public event Action F { readonly add {} readonly remove {} } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int P", FeaturesResources.property_), - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int Q", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly add", FeaturesResources.event_accessor), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly remove", FeaturesResources.event_accessor)); - } - [Fact] public void PartialMember_DeleteInsert_SingleDocument() { @@ -4753,6 +4804,8 @@ event Action E { add {} remove {} } semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("M"), preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P1").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P1").SetMethod, preserveLocalVariables: false), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").GetMethod, preserveLocalVariables: false), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").SetMethod, preserveLocalVariables: false), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Int32").GetMethod, preserveLocalVariables: false), @@ -5086,6 +5139,200 @@ public void PartialMember_DeleteInsert_ConstructorWithInitializers() #region Methods + [Theory] + [InlineData("static")] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Method_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int F() => 0; }"; + var src2 = "class C { " + newModifiers + "int F() => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int F() => 0;]@10 -> [" + newModifiers + "int F() => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "int F()", FeaturesResources.method)); + } + + [Fact] + public void Method_NewModifier_Add() + { + var src1 = "class C { int F() => 0; }"; + var src2 = "class C { new int F() => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [int F() => 0;]@10 -> [new int F() => 0;]@10"); + + // Currently, an edit is produced eventhough there is no metadata/IL change. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"))); + } + + [Fact] + public void Method_NewModifier_Remove() + { + var src1 = "class C { new int F() => 0; }"; + var src2 = "class C { int F() => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [new int F() => 0;]@10 -> [int F() => 0;]@10"); + + // Currently, an edit is produced eventhough there is no metadata/IL change. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"))); + } + + [Fact] + public void Method_ReadOnlyModifier_Add_InMutableStruct() + { + var src1 = @" +struct S +{ + public int M() => 1; +}"; + var src2 = @" +struct S +{ + public readonly int M() => 1; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method)); + } + + [Fact] + public void Method_ReadOnlyModifier_Add_InReadOnlyStruct1() + { + var src1 = @" +readonly struct S +{ + public int M() + => 1; +}"; + var src2 = @" +readonly struct S +{ + public readonly int M() + => 1; +}"; + + var edits = GetTopEdits(src1, src2); + + // Currently, an edit is produced eventhough the body nor IsReadOnly attribute have changed. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("M"))); + } + + [Fact] + public void Method_ReadOnlyModifier_Add_InReadOnlyStruct2() + { + var src1 = @" +readonly struct S +{ + public int M() => 1; +}"; + var src2 = @" +struct S +{ + public readonly int M() => 1; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "struct S", "struct")); + } + + [Fact] + public void Method_AsyncModifier_Remove() + { + var src1 = @" +class Test +{ + public async Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public Task WaitAsync() + { + return Task.FromResult(1); + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "public Task WaitAsync()", FeaturesResources.method)); + } + + [Fact] + public void Method_AsyncModifier_Add() + { + var src1 = @" +class Test +{ + public Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public async Task WaitAsync() + { + await Task.Delay(1000); + return 1; + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics(); + + VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); + } + + [Fact] + public void Method_AsyncModifier_Add_NotSupported() + { + var src1 = @" +class Test +{ + public Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public async Task WaitAsync() + { + await Task.Delay(1000); + return 1; + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.MakeMethodAsync, "public async Task WaitAsync()")); + } + [Fact] public void Method_Update() { @@ -5581,11 +5828,56 @@ public void ExternMethodInsert() var src1 = @" using System; using System.Runtime.InteropServices; - -class C -{ -}"; - var src2 = @" + +class C +{ +}"; + var src2 = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + [DllImport(""msvcrt.dll"")] + private static extern int puts(string c); +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + @"Insert [[DllImport(""msvcrt.dll"")] + private static extern int puts(string c);]@74", + "Insert [(string c)]@135", + "Insert [string c]@136"); + + // CLR doesn't support methods without a body + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertExtern, "private static extern int puts(string c)", FeaturesResources.method)); + } + + [Fact] + [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] + public void ExternMethodDeleteInsert() + { + var srcA1 = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + [DllImport(""msvcrt.dll"")] + private static extern int puts(string c); +}"; + var srcA2 = @" +using System; +using System.Runtime.InteropServices; +"; + + var srcB1 = @" +using System; +using System.Runtime.InteropServices; +"; + var srcB2 = @" using System; using System.Runtime.InteropServices; @@ -5595,22 +5887,22 @@ class C private static extern int puts(string c); } "; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - @"Insert [[DllImport(""msvcrt.dll"")] - private static extern int puts(string c);]@74", - "Insert [(string c)]@135", - "Insert [string c]@136"); - - // CLR doesn't support methods without a body - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertExtern, "private static extern int puts(string c)", FeaturesResources.method)); + // TODO: The method does not need to be updated since there are no sequence points generated for it. + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.puts")), + }) + }); } - [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] [Fact] - public void ExternMethodDeleteInsert() + [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] + public void ExternMethod_Attribute_DeleteInsert() { var srcA1 = @" using System; @@ -5637,6 +5929,7 @@ class C class C { [DllImport(""msvcrt.dll"")] + [Obsolete] private static extern int puts(string c); } "; @@ -5645,8 +5938,12 @@ class C new[] { DocumentResults(), - DocumentResults() - }); + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.puts")), + }) + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } [Fact] @@ -5872,82 +6169,6 @@ static void Main() { } Diagnostic(RudeEditKind.Delete, "static void Main()", FeaturesResources.parameter)); } - [Fact] - public void MethodUpdate_Modifier_Async_Remove() - { - var src1 = @" -class Test -{ - public async Task WaitAsync() - { - return 1; - } -}"; - var src2 = @" -class Test -{ - public Task WaitAsync() - { - return Task.FromResult(1); - } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "public Task WaitAsync()", FeaturesResources.method)); - } - - [Fact] - public void MethodUpdate_Modifier_Async_Add() - { - var src1 = @" -class Test -{ - public Task WaitAsync() - { - return 1; - } -}"; - var src2 = @" -class Test -{ - public async Task WaitAsync() - { - await Task.Delay(1000); - return 1; - } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics(); - - VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); - } - - [Fact] - public void MethodUpdate_Modifier_Async_Add_NotSupported() - { - var src1 = @" -class Test -{ - public Task WaitAsync() - { - return 1; - } -}"; - var src2 = @" -class Test -{ - public async Task WaitAsync() - { - await Task.Delay(1000); - return 1; - } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - capabilities: EditAndContinueTestHelpers.BaselineCapabilities, - Diagnostic(RudeEditKind.MakeMethodAsync, "public async Task WaitAsync()")); - } - [Fact] public void MethodUpdate_AsyncMethod0() { @@ -7221,6 +7442,34 @@ public void PartialMethod_Insert() #region Operators + [Theory] + [InlineData("implicit", "explicit")] + [InlineData("explicit", "implicit")] + public void Operator_Modifiers_Update(string oldModifiers, string newModifiers) + { + var src1 = "class C { public static " + oldModifiers + " operator int (C c) => 0; }"; + var src2 = "class C { public static " + newModifiers + " operator int (C c) => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [public static " + oldModifiers + " operator int (C c) => 0;]@10 -> [public static " + newModifiers + " operator int (C c) => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public static " + newModifiers + " operator int (C c)", CSharpFeaturesResources.conversion_operator)); + } + + [Fact] + public void Operator_Conversion_ExternModifiers_Add() + { + var src1 = "class C { public static implicit operator bool (C c) => default; }"; + var src2 = "class C { extern public static implicit operator bool (C c); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.MethodBodyDelete, "extern public static implicit operator bool (C c)", CSharpFeaturesResources.conversion_operator)); + } + [Fact] public void OperatorInsert() { @@ -7494,6 +7743,23 @@ public void Operator_ReadOnlyRef_Parameter_Update() #region Constructor, Destructor + [Fact] + [WorkItem(2068, "https://github.com/dotnet/roslyn/issues/2068")] + public void Constructor_ExternModifier_Add() + { + var src1 = "class C { }"; + var src2 = "class C { public extern C(); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [public extern C();]@10", + "Insert [()]@25"); + + // The compiler generates an empty constructor. + edits.VerifySemanticDiagnostics(); + } + [Fact] public void ConstructorInitializer_Update1() { @@ -7634,7 +7900,7 @@ public void DestructorDelete_InsertConstructor() "Delete [~B() { }]@10"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "B()", FeaturesResources.constructor), + Diagnostic(RudeEditKind.ChangingAccessibility, "B()", FeaturesResources.constructor), Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); } @@ -7682,15 +7948,15 @@ public void InstanceCtorDelete_Public() [InlineData("internal")] [InlineData("private protected")] [InlineData("protected internal")] - public void InstanceCtorDelete_NonPublic(string visibility) + public void InstanceCtorDelete_NonPublic(string accessibility) { - var src1 = "class C { " + visibility + " C() { } }"; + var src1 = "class C { " + accessibility + " C() { } }"; var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -7810,7 +8076,7 @@ public void InstanceCtorInsert_Private_Implicit1() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "private C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "private C()", FeaturesResources.constructor)); } [Fact] @@ -7822,7 +8088,7 @@ public void InstanceCtorInsert_Private_Implicit2() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "C()", FeaturesResources.constructor)); } [Fact] @@ -7834,7 +8100,7 @@ public void InstanceCtorInsert_Protected_PublicImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "protected C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "protected C()", FeaturesResources.constructor)); } [Fact] @@ -7846,7 +8112,7 @@ public void InstanceCtorInsert_Internal_PublicImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "internal C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor)); } [Fact] @@ -7858,7 +8124,7 @@ public void InstanceCtorInsert_Internal_ProtectedImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "internal C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor)); } [Fact] @@ -8012,12 +8278,12 @@ public void InstanceCtor_Partial_DeletePrivateInsertPublic() new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - // delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed visibility + // delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed accessibility DocumentResults( semanticEdits: NoSemanticEdits), DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "public C()", FeaturesResources.constructor) }), + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingAccessibility, "public C()", FeaturesResources.constructor) }), }); } @@ -8155,9 +8421,9 @@ public void InstanceCtor_Partial_InsertPublicDeletePrivate() new[] { DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "public C()", FeaturesResources.constructor) }), + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingAccessibility, "public C()", FeaturesResources.constructor) }), - // delete of the constructor in partial part will be reported as rude in the the other document where it was inserted with changed visibility + // delete of the constructor in partial part will be reported as rude in the the other document where it was inserted with changed accessibility DocumentResults(), }); } @@ -8176,7 +8442,7 @@ public void InstanceCtor_Partial_InsertInternalDeletePrivate() new[] { DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "internal C()", FeaturesResources.constructor) }), + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor) }), DocumentResults(), }); @@ -8581,23 +8847,6 @@ public void InstanceCtor_Partial_Implicit_Update() }); } - [WorkItem(2068, "https://github.com/dotnet/roslyn/issues/2068")] - [Fact] - public void Insert_ExternConstruct() - { - var src1 = "class C { }"; - var src2 = "class C { public extern C(); }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Insert [public extern C();]@10", - "Insert [()]@25"); - - // The compiler generates an empty constructor. - edits.VerifySemanticDiagnostics(); - } - [Fact] public void ParameterlessConstructor_SemanticError_Delete1() { @@ -8616,7 +8865,7 @@ class C // The compiler interprets D() as a constructor declaration. edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -9070,7 +9319,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Private() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -9082,7 +9331,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate_Private() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -10469,6 +10718,109 @@ public void FieldUpdate_FieldKind() Diagnostic(RudeEditKind.FieldKindUpdate, "event Action a", CSharpFeaturesResources.event_field)); } + [Theory] + [InlineData("static")] + [InlineData("volatile")] + [InlineData("const")] + public void Field_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int F = 0; }"; + var src2 = "class C { " + newModifiers + "int F = 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int F = 0;]@10 -> [" + newModifiers + "int F = 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "F = 0", FeaturesResources.field)); + } + + [Fact] + public void Field_Modifier_Add_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int F; }"; + + var srcA2 = "partial class C { static int F; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.ModifiersUpdate, "F", FeaturesResources.field) + }), + + DocumentResults(), + }); + } + + [Fact] + public void Field_Attribute_Add_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int F; }"; + + var srcA2 = "partial class C { [System.Obsolete]int F; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")) + }), + + DocumentResults(), + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + + [Fact] + public void Field_FixedSize_Update() + { + var src1 = "struct S { public unsafe fixed byte bs[1]; }"; + var src2 = "struct S { public unsafe fixed byte bs[2]; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [bs[1]]@36 -> [bs[2]]@36"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.FixedSizeFieldUpdate, "bs[2]", FeaturesResources.field)); + } + + [WorkItem(1120407, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1120407")] + [Fact] + public void Field_Const_Update() + { + var src1 = "class C { const int x = 0; }"; + var src2 = "class C { const int x = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [x = 0]@20 -> [x = 1]@20"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "x = 1", FeaturesResources.const_field)); + } + [Fact] public void EventFieldUpdate_VariableDeclarator() { @@ -10956,7 +11308,7 @@ public void FieldInsert_Static_NotSupportedByRuntime() } [Fact] - public void FieldUpdate_AddAttribute_Multiple() + public void Field_Attribute_Add_NotSupportedByRuntime() { var src1 = @" class C @@ -10966,40 +11318,91 @@ class C var src2 = @" class C { - [System.Obsolete]public int a = 1, x = 1; + [System.Obsolete]public int a = 1, x = 1; +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [public int a = 1, x = 1;]@18 -> [[System.Obsolete]public int a = 1, x = 1;]@18"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "a = 1", FeaturesResources.field)); + } + + [Fact] + public void Field_Attribute_Add() + { + var src1 = @" +class C +{ + public int a; +}"; + var src2 = @" +class C +{ + [System.Obsolete]public int a; }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [public int a = 1, x = 1;]@18 -> [[System.Obsolete]public int a = 1, x = 1;]@18"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "a = 1", FeaturesResources.field)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")) + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } [Fact] - public void FieldUpdate_AddAttribute_SupportedByRuntime() + public void Field_Attribute_Add_WithInitializer() { var src1 = @" class C { - public int a; + int a; }"; var src2 = @" class C { - [System.Obsolete]public int a; + [System.Obsolete]int a = 0; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")) + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true), }, - capabilities: EditAndContinueTestHelpers.Net5RuntimeCapabilities | EditAndContinueCapabilities.ChangeCustomAttributes); + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + + [Fact] + public void Field_Attribute_DeleteInsertUpdate_WithInitializer() + { + var srcA1 = "partial class C { int a = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { [System.Obsolete]int a = 2; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } [Fact] @@ -11118,6 +11521,35 @@ public void EventField_Partial_InsertDelete() #region Properties + [Theory] + [InlineData("static")] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Property_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int F => 0; }"; + var src2 = "class C { " + newModifiers + "int F => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int F => 0;]@10 -> [" + newModifiers + "int F => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "int F", FeaturesResources.property_)); + } + [Fact] public void PropertyWithExpressionBody_Update() { @@ -11977,11 +12409,20 @@ public void AutoProperty_Partial_InsertDelete() var srcA2 = "partial class C { int P { get; set; } int Q { get; init; } }"; var srcB2 = "partial class C { }"; + // Accessors need to be updated even though they do not have an explicit body. + // There is still a sequence point generated for them whose location needs to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Q").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Q").SetMethod), + }), DocumentResults(), }); } @@ -11995,12 +12436,19 @@ public void AutoPropertyWithInitializer_Partial_InsertDelete() var srcA2 = "partial class C { int P { get; set; } = 1; }"; var srcB2 = "partial class C { }"; + // Accessors need to be updated even though they do not have an explicit body. + // There is still a sequence point generated for them whose location needs to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), DocumentResults(), }); @@ -12026,10 +12474,114 @@ public void PropertyWithExpressionBody_Partial_InsertDeleteUpdate() }); } + [Fact] + public void AutoProperty_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int P { get; } +}"; + var src2 = @" +struct S +{ + readonly int P { get; } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics(); + } + + [Fact] + public void Property_InMutableStruct_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int P1 { get => 1; } + int P2 { get => 1; set {}} + int P3 { get => 1; set {}} + int P4 { get => 1; set {}} +}"; + var src2 = @" +struct S +{ + readonly int P1 { get => 1; } + int P2 { readonly get => 1; set {}} + int P3 { get => 1; readonly set {}} + readonly int P4 { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.property_setter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter)); + } + + [Fact] + public void Property_InReadOnlyStruct_ReadOnly_Add() + { + // indent to align accessor bodies and avoid updates caused by sequence point location changes + + var src1 = @" +readonly struct S +{ + int P1 { get => 1; } + int P2 { get => 1; set {}} + int P3 { get => 1; set {}} + int P4 { get => 1; set {}} +}"; + var src2 = @" +readonly struct S +{ + readonly int P1 { get => 1; } + int P2 { readonly get => 1; set {}} + int P3 { get => 1; readonly set {}} + readonly int P4 { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + + // updates only for accessors whose modifiers were explicitly updated + edits.VerifySemantics(new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("P2").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("P3").SetMethod, preserveLocalVariables: false) + }); + } + #endregion #region Indexers + [Theory] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Indexer_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int this[int a] => 0; }"; + var src2 = "class C { " + newModifiers + "int this[int a] => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int this[int a] => 0;]@10 -> [" + newModifiers + "int this[int a] => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "int this[int a]", FeaturesResources.indexer_)); + } + [Fact] public void Indexer_GetterUpdate() { @@ -12694,49 +13246,6 @@ public void Indexer_Insert() edits.VerifySemanticDiagnostics(); } - [WorkItem(1120407, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1120407")] - [Fact] - public void ConstField_Update() - { - var src1 = "class C { const int x = 0; }"; - var src2 = "class C { const int x = 1; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits("Update [x = 0]@20 -> [x = 1]@20"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Update, "x = 1", FeaturesResources.const_field)); - } - - [Fact] - public void ConstField_Delete() - { - var src1 = "class C { const int x = 0; }"; - var src2 = "class C { int x = 0; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits("Update [const int x = 0;]@10 -> [int x = 0;]@10"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "int x = 0", FeaturesResources.field)); - } - - [Fact] - public void ConstField_Add() - { - var src1 = "class C { int x = 0; }"; - var src2 = "class C { const int x = 0; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits("Update [int x = 0;]@10 -> [const int x = 0;]@10"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "const int x = 0", FeaturesResources.const_field)); - } - [Fact] public void Indexer_ReadOnlyRef_Parameter_InsertWhole() { @@ -12851,16 +13360,23 @@ public void IndexerInit_Partial_InsertDelete() public void AutoIndexer_Partial_InsertDelete() { var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { int this[int x] { get; set; } int Q { get; init; } }"; + var srcB1 = "partial class C { int this[int x] { get; set; } }"; - var srcA2 = "partial class C { int this[int x] { get; set; } int Q { get; init; } }"; + var srcA2 = "partial class C { int this[int x] { get; set; } }"; var srcB2 = "partial class C { }"; + // Accessors need to be updated even though they do not have an explicit body. + // There is still a sequence point generated for them whose location needs to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod), + }), DocumentResults(), }); } @@ -12897,10 +13413,116 @@ partial class C }); } + [Fact] + public void AutoIndexer_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int this[int x] { get; } +}"; + var src2 = @" +struct S +{ + readonly int this[int x] { get; } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.indexer_getter)); + } + + [Fact] + public void Indexer_InMutableStruct_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int this[int x] { get => 1; } + int this[uint x] { get => 1; set {}} + int this[byte x] { get => 1; set {}} + int this[sbyte x] { get => 1; set {}} +}"; + var src2 = @" +struct S +{ + readonly int this[int x] { get => 1; } + int this[uint x] { readonly get => 1; set {}} + int this[byte x] { get => 1; readonly set {}} + readonly int this[sbyte x] { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.indexer_setter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.indexer_setter)); + } + + [Fact] + public void Indexer_InReadOnlyStruct_ReadOnly_Add() + { + // indent to align accessor bodies and avoid updates caused by sequence point location changes + + var src1 = @" +readonly struct S +{ + int this[int x] { get => 1; } + int this[uint x] { get => 1; set {}} + int this[byte x] { get => 1; set {}} + int this[sbyte x] { get => 1; set {}} +}"; + var src2 = @" +readonly struct S +{ + readonly int this[int x] { get => 1; } + int this[uint x] { readonly get => 1; set {}} + int this[byte x] { get => 1; readonly set {}} + readonly int this[sbyte x] { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + + // updates only for accessors whose modifiers were explicitly updated + edits.VerifySemantics(new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMembers("this[]").Cast().Single(m => m.Parameters.Single().Type.Name == "UInt32").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMembers("this[]").Cast().Single(m => m.Parameters.Single().Type.Name == "Byte").SetMethod, preserveLocalVariables: false) + }); + } + #endregion #region Events + [Theory] + [InlineData("static")] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Event_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "event Action F { add {} remove {} } }"; + var src2 = "class C { " + newModifiers + "event Action F { add {} remove {} } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "event Action F { add {} remove {} }]@10 -> [" + newModifiers + "event Action F { add {} remove {} }]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "event Action F", FeaturesResources.event_)); + } + [Fact] public void EventAccessorReorder1() { @@ -13074,6 +13696,45 @@ public void Event_Partial_InsertDelete() }); } + [Fact] + public void Event_InMutableStruct_ReadOnly_Add() + { + var src1 = @" +struct S +{ + public event Action E { add {} remove {} } +}"; + var src2 = @" +struct S +{ + public readonly event Action E { add {} remove {} } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly event Action E", FeaturesResources.event_)); + } + + [Fact] + public void Event_InReadOnlyStruct_ReadOnly_Add1() + { + var src1 = @" +readonly struct S +{ + public event Action E { add {} remove {} } +}"; + var src2 = @" +readonly struct S +{ + public readonly event Action E { add {} remove {} } +}"; + var edits = GetTopEdits(src1, src2); + + // Currently, an edit is produced eventhough bodies nor IsReadOnly attribute have changed. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("E").AddMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("E").RemoveMethod)); + } + #endregion #region Parameter diff --git a/src/EditorFeatures/CSharpTest/EditorConfigSettings/DataProvider/DataProviderTests.cs b/src/EditorFeatures/CSharpTest/EditorConfigSettings/DataProvider/DataProviderTests.cs index 66842a26d5565..5bc456ab6687f 100644 --- a/src/EditorFeatures/CSharpTest/EditorConfigSettings/DataProvider/DataProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/EditorConfigSettings/DataProvider/DataProviderTests.cs @@ -108,7 +108,7 @@ public void TestGettingCodeStyleSettingsProviderLanguageServiceAsync() var model = new TestViewModel(); settingsProvider.RegisterViewModel(model); var dataSnapShot = settingsProvider.GetCurrentDataSnapshot(); - Assert.Equal(29, dataSnapShot.Length); + Assert.Equal(30, dataSnapShot.Length); } [Fact, Trait(Traits.Feature, Traits.Features.EditorConfigUI)] diff --git a/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs b/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs index 792917787cdca..bcb254453e86a 100644 --- a/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs +++ b/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs @@ -35,7 +35,7 @@ private class TestWithDialog : VerifyCS.Test public ImmutableArray MemberNames; public Action> OptionsCallback; - public override AdhocWorkspace CreateWorkspace() + protected override Workspace CreateWorkspaceImpl() { // If we're a dialog test, then mixin our mock and initialize its values to the ones the test asked for. var workspace = new AdhocWorkspace(s_composition.GetHostServices()); @@ -98,15 +98,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(39916, "https://github.com/dotnet/roslyn/issues/39916")] @@ -135,15 +133,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferExplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferExplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -179,15 +175,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -229,15 +223,13 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -273,15 +265,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -309,15 +299,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -345,15 +333,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -386,15 +372,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -426,15 +410,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -484,16 +466,14 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -551,15 +531,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -795,15 +773,13 @@ public override bool Equals(object obj) } "; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = expected, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -860,16 +836,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -902,16 +876,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -955,16 +927,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1005,17 +975,15 @@ public override int GetHashCode() } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, MemberNames = ImmutableArray.Empty, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1088,16 +1056,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1130,16 +1096,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1180,16 +1144,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1374,16 +1336,14 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1411,15 +1371,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1447,15 +1405,13 @@ public override bool Equals(object obj) } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1488,16 +1444,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1530,16 +1484,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1580,16 +1532,14 @@ struct Bar { }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1632,16 +1582,14 @@ struct Bar public override int GetHashCode() => 0; }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1682,16 +1630,14 @@ struct Bar { }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1724,16 +1670,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1766,16 +1710,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1816,16 +1758,14 @@ enum Bar { }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1858,16 +1798,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1899,16 +1837,14 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = ImmutableArray.Create("a", "b"), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1942,16 +1878,14 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = ImmutableArray.Create("c", "b"), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -1983,16 +1917,14 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = ImmutableArray.Empty, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(17643, "https://github.com/dotnet/roslyn/issues/17643")] @@ -2020,15 +1952,13 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(25690, "https://github.com/dotnet/roslyn/issues/25690")] @@ -2188,17 +2118,15 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2278,17 +2206,15 @@ public override bool Equals(object obj) public static bool operator {|CS0216:==|}(Program left, Program right) => true; }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => Assert.Null(options.FirstOrDefault(i => i.Id == GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId)), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2333,17 +2259,15 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2392,16 +2316,14 @@ enum Bar { }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2466,16 +2388,14 @@ struct Bar : IEquatable public static bool operator !=(Bar left, Bar right) => !(left == right); }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2510,17 +2430,15 @@ public bool Equals(Program other) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2711,17 +2629,15 @@ public bool Equals(Program other) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -2844,17 +2760,15 @@ public override bool Equals(object obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => Assert.Null(options.FirstOrDefault(i => i.Id == GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId)), LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -3063,16 +2977,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -3121,7 +3033,7 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedState = @@ -3135,10 +3047,8 @@ public override int GetHashCode() }, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(37297, "https://github.com/dotnet/roslyn/issues/37297")] @@ -3186,7 +3096,7 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestState = { @@ -3211,10 +3121,8 @@ public override int GetHashCode() }, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(37297, "https://github.com/dotnet/roslyn/issues/37297")] @@ -3262,7 +3170,7 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestState = { @@ -3279,10 +3187,8 @@ public override int GetHashCode() FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -3338,7 +3244,7 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedState = @@ -3352,10 +3258,8 @@ public override int GetHashCode() }, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -3422,16 +3326,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(39916, "https://github.com/dotnet/roslyn/issues/39916")] @@ -3499,16 +3401,14 @@ public override int GetHashCode() } }"; - var test = new VerifyCS.Test + await new VerifyCS.Test { TestCode = code, FixedCode = fixedCode, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp6, - }; - - test.Options.AddRange(PreferExplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferExplicitTypeWithInfo() }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)] @@ -3693,7 +3593,7 @@ public override bool Equals(object? obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedState = @@ -3710,10 +3610,8 @@ public override bool Equals(object? obj) MemberNames = default, OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId), LanguageVersion = LanguageVersion.Default, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(40053, "https://github.com/dotnet/roslyn/issues/40053")] @@ -3761,17 +3659,15 @@ public override bool Equals(object? obj) } }"; - var test = new TestWithDialog + await new TestWithDialog { TestCode = code, FixedCode = fixedCode, MemberNames = default, OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId), LanguageVersion = LanguageVersion.Default, - }; - - test.Options.AddRange(PreferImplicitTypeWithInfo()); - await test.RunAsync(); + Options = { PreferImplicitTypeWithInfo() }, + }.RunAsync(); } [WorkItem(42574, "https://github.com/dotnet/roslyn/issues/42574")] diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementExplicitlyTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementExplicitlyTests.cs index 7e7b0806bdaea..511f26564b02e 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementExplicitlyTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementExplicitlyTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ImplementInterface { - public partial class ImplementExplicitlyTests : AbstractCSharpCodeActionTest + public class ImplementExplicitlyTests : AbstractCSharpCodeActionTest { private const int SingleMember = 0; private const int SameInterface = 1; diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementImplicitlyTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementImplicitlyTests.cs index 9e5cf1cf288a1..d46a05aa7684a 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementImplicitlyTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementImplicitlyTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ImplementInterface { - public partial class ImplementImplicitlyTests : AbstractCSharpCodeActionTest + public class ImplementImplicitlyTests : AbstractCSharpCodeActionTest { private const int SingleMember = 0; private const int SameInterface = 1; diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index 433012586fa24..c8065add4eb18 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -3,37 +3,27 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.ImplementInterface; -using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles; using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; using Xunit; -using Xunit.Abstractions; +using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeFixVerifier< + Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, + Microsoft.CodeAnalysis.CSharp.ImplementInterface.CSharpImplementInterfaceCodeFixProvider>; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ImplementInterface { - public partial class ImplementInterfaceTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public class ImplementInterfaceTests { private readonly NamingStylesTestOptionSets _options = new NamingStylesTestOptionSets(LanguageNames.CSharp); - public ImplementInterfaceTests(ITestOutputHelper logger) - : base(logger) - { - } - - internal override (DiagnosticAnalyzer?, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (null, new CSharpImplementInterfaceCodeFixProvider()); - - private OptionsCollection AllOptionsOff - => new OptionsCollection(GetLanguage()) + private static OptionsCollection AllOptionsOff + => new OptionsCollection(LanguageNames.CSharp) { { CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, { CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, @@ -43,8 +33,8 @@ private OptionsCollection AllOptionsOff { CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, }; - private OptionsCollection AllOptionsOn - => new OptionsCollection(GetLanguage()) + private static OptionsCollection AllOptionsOn + => new OptionsCollection(LanguageNames.CSharp) { { CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement }, { CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement }, @@ -54,8 +44,8 @@ private OptionsCollection AllOptionsOn { CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement }, }; - private OptionsCollection AccessorOptionsOn - => new OptionsCollection(GetLanguage()) + private static OptionsCollection AccessorOptionsOn + => new OptionsCollection(LanguageNames.CSharp) { { CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, { CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, @@ -65,77 +55,52 @@ private OptionsCollection AccessorOptionsOn { CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, }; - private static readonly ParseOptions CSharp7_1 = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_1); - - private const string NullableAttributesCode = @" -namespace System.Diagnostics.CodeAnalysis -{ - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] - internal sealed class AllowNullAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] - internal sealed class DisallowNullAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] - internal sealed class MaybeNullAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] - internal sealed class NotNullAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class MaybeNullWhenAttribute : Attribute - { - public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - public bool ReturnValue { get; } - } - - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class NotNullWhenAttribute : Attribute - { - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - public bool ReturnValue { get; } - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] - internal sealed class NotNullIfNotNullAttribute : Attribute - { - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - public string ParameterName { get; } - } - - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - internal sealed class DoesNotReturnAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class DoesNotReturnIfAttribute : Attribute - { - public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; - public bool ParameterValue { get; } - } -}"; - - internal async Task TestWithAllCodeStyleOptionsOffAsync( + internal static async Task TestWithAllCodeStyleOptionsOffAsync( string initialMarkup, string expectedMarkup, - int index = 0, ParseOptions? parseOptions = null) + (string equivalenceKey, int index)? codeAction = null) { - await TestAsync(initialMarkup, expectedMarkup, parseOptions, null, - index, options: AllOptionsOff); + await new VerifyCS.Test + { + TestCode = initialMarkup, + FixedCode = expectedMarkup, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = codeAction?.equivalenceKey, + CodeActionIndex = codeAction?.index, + }.RunAsync(); } - internal async Task TestWithAllCodeStyleOptionsOnAsync( - string initialMarkup, string expectedMarkup, - int index = 0, ParseOptions? parseOptions = null) + internal static async Task TestWithAllCodeStyleOptionsOnAsync(string initialMarkup, string expectedMarkup) { - await TestAsync(initialMarkup, expectedMarkup, parseOptions, null, - index, options: AllOptionsOn); + await new VerifyCS.Test + { + TestCode = initialMarkup, + FixedCode = expectedMarkup, + Options = { AllOptionsOn }, + }.RunAsync(); } - internal async Task TestWithAccessorCodeStyleOptionsOnAsync( - string initialMarkup, string expectedMarkup, - int index = 0, ParseOptions? parseOptions = null) + internal static async Task TestWithAccessorCodeStyleOptionsOnAsync(string initialMarkup, string expectedMarkup) + { + await new VerifyCS.Test + { + TestCode = initialMarkup, + FixedCode = expectedMarkup, + Options = { AccessorOptionsOn }, + }.RunAsync(); + } + + private static async Task TestInRegularAndScriptAsync( + string initialMarkup, + string expectedMarkup, + (string equivalenceKey, int index)? codeAction = null) { - await TestAsync(initialMarkup, expectedMarkup, parseOptions, null, - index, options: AccessorOptionsOn); + await new VerifyCS.Test + { + TestCode = initialMarkup, + FixedCode = expectedMarkup, + CodeActionEquivalenceKey = codeAction?.equivalenceKey, + CodeActionIndex = codeAction?.index, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -147,7 +112,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", @"interface IInterface @@ -167,16 +132,18 @@ public void Method1() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestMethodInRecord() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.Preview, + TestCode = @"interface IInterface { void Method1(); } -record Record : [|IInterface|] +record Record : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); } @@ -187,7 +154,8 @@ public void Method1() { throw new System.NotImplementedException(); } -}", parseOptions: TestOptions.RegularPreview); +}", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -210,19 +178,21 @@ public NativeIntegerAttribute(bool[] flags) }"; // Note: we're putting the attribute by hand to simulate metadata - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + TestCode = @"interface IInterface { - [return: System.Runtime.CompilerServices.NativeInteger(new[] { true, true })] + [return: {|CS8335:System.Runtime.CompilerServices.NativeInteger(new[] { true, true })|}] (nint, nuint) Method(nint x, nuint x2); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }" + nativeIntegerAttributeDefinition, - @"interface IInterface + FixedCode = @"interface IInterface { - [return: System.Runtime.CompilerServices.NativeInteger(new[] { true, true })] + [return: {|CS8335:System.Runtime.CompilerServices.NativeInteger(new[] { true, true })|}] (nint, nuint) Method(nint x, nuint x2); } @@ -232,7 +202,9 @@ class Class : IInterface { throw new System.NotImplementedException(); } -}" + nativeIntegerAttributeDefinition); +}" + nativeIntegerAttributeDefinition, + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -244,7 +216,7 @@ await TestWithAllCodeStyleOptionsOffAsync( (int, int) Method((string, string) x); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", @"interface IInterface @@ -272,7 +244,7 @@ interface I { ValueTuple F(); } -class C : [|I|] +class C : {|CS0535:I|} { }", @" @@ -299,7 +271,7 @@ await TestWithAllCodeStyleOptionsOnAsync( void Method1(); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", @"interface IInterface @@ -313,14 +285,6 @@ class Class : IInterface }"); } - private const string s_tupleElementNamesAttribute = -@"namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Event )] - public sealed class TupleElementNamesAttribute : Attribute { } -} -"; - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface), CompilerTrait(CompilerFeature.Tuples)] public async Task TupleWithNamesInMethod() { @@ -328,16 +292,16 @@ public async Task TupleWithNamesInMethod() await TestWithAllCodeStyleOptionsOffAsync( @"interface IInterface { - [return: System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] + [return: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] (int a, int b)[] Method1((int c, string) x); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { -}" + s_tupleElementNamesAttribute, +}", @"interface IInterface { - [return: System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] + [return: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] (int a, int b)[] Method1((int c, string) x); } @@ -347,8 +311,7 @@ class Class : IInterface { throw new System.NotImplementedException(); } -} -" + s_tupleElementNamesAttribute); +}"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface), CompilerTrait(CompilerFeature.Tuples)] @@ -357,16 +320,16 @@ public async Task TupleWithNamesInMethod_Explicitly() await TestWithAllCodeStyleOptionsOffAsync( @"interface IInterface { - [return: System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] + [return: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] (int a, int b)[] Method1((int c, string) x); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { -}" + s_tupleElementNamesAttribute, +}", @"interface IInterface { - [return: System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] + [return: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] (int a, int b)[] Method1((int c, string) x); } @@ -376,9 +339,8 @@ class Class : IInterface { throw new System.NotImplementedException(); } -} -" + s_tupleElementNamesAttribute, -index: 1); +}", +codeAction: ("True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface), CompilerTrait(CompilerFeature.Tuples)] @@ -387,17 +349,17 @@ public async Task TupleWithNamesInProperty() await TestWithAllCodeStyleOptionsOffAsync( @"interface IInterface { - [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] - (int a, int b)[] Property1 { [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] get; [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] set; } + [{|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] + (int a, int b)[] Property1 { [return: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] get; [param: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] set; } } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { -}" + s_tupleElementNamesAttribute, +}", @"interface IInterface { - [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] - (int a, int b)[] Property1 { [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] get; [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] set; } + [{|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] + (int a, int b)[] Property1 { [return: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] get; [param: {|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] set; } } class Class : IInterface @@ -414,34 +376,37 @@ class Class : IInterface throw new System.NotImplementedException(); } } -} -" + s_tupleElementNamesAttribute); +}"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface), CompilerTrait(CompilerFeature.Tuples)] public async Task TupleWithNamesInEvent() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + TestCode = @"interface IInterface { - [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] - event Func<(int a, int b)> Event1; + [{|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] + event System.Func<(int a, int b)> Event1; } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { -}" + s_tupleElementNamesAttribute, -@"interface IInterface +}", + FixedCode = @"using System; + +interface IInterface { - [System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })] - event Func<(int a, int b)> Event1; + [{|CS8138:System.Runtime.CompilerServices.TupleElementNames(new[] { ""a"", ""b"" })|}] + event System.Func<(int a, int b)> Event1; } class Class : IInterface { public event Func<(int a, int b)> Event1; -} -" + s_tupleElementNamesAttribute); +}", + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -450,16 +415,16 @@ public async Task NoDynamicAttributeInMethod() await TestWithAllCodeStyleOptionsOffAsync( @"interface IInterface { - [return: System.Runtime.CompilerServices.DynamicAttribute()] + [return: {|CS1970:System.Runtime.CompilerServices.DynamicAttribute()|}] object Method1(); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", @"interface IInterface { - [return: System.Runtime.CompilerServices.DynamicAttribute()] + [return: {|CS1970:System.Runtime.CompilerServices.DynamicAttribute()|}] object Method1(); } @@ -475,32 +440,48 @@ public object Method1() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task NoNullableAttributesInMethodFromMetadata() { - var initial = @" - - - - -#nullable enable - -public interface IInterface -{ - void M(string? s1, string s2); - string this[string? s1, string s2] { get; set; } -} - - - + var test = new VerifyCS.Test + { + TestState = + { + Sources = + { + @" #nullable enable using System; -class C : [|IInterface|] +class C : {|CS0535:{|CS0535:IInterface|}|} { -} - -"; +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @" +#nullable enable - var expected = @" +public interface IInterface +{ + void M(string? s1, string s2); + string this[string? s1, string s2] { get; set; } +}" + }, + }, + }, + AdditionalProjectReferences = + { + "Assembly1", + }, + }, + FixedState = + { + Sources = + { + @" #nullable enable using System; @@ -524,9 +505,15 @@ public void M(string? s1, string s2) { throw new NotImplementedException(); } -}"; +}", + }, + }, + CodeActionEquivalenceKey = "False;False;True:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }; - await TestWithAllCodeStyleOptionsOffAsync(initial, expected, index: 0); + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -538,7 +525,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -class Class : [|IInterface|]", +class Class : {|CS0535:IInterface|}{|CS1513:|}{|CS1514:|}", @"interface IInterface { void Method1(); @@ -567,7 +554,7 @@ interface IInterface2 : IInterface1 { } -class Class : [|IInterface2|] +class Class : {|CS0535:IInterface2|} { }", @"interface IInterface1 @@ -601,7 +588,7 @@ interface IInterface2 : IInterface1 void Method1(); } -class Class : [|IInterface2|] +class Class : {|CS0535:IInterface2|} { }", @"interface IInterface1 @@ -636,7 +623,7 @@ interface IInterface2 : IInterface1 void Method2(); } -class Class : [|IInterface2|] +class Class : {|CS0535:{|CS0535:IInterface2|}|} { }", @"interface IInterface1 @@ -677,7 +664,7 @@ interface IInterface2 : IInterface1 void Method1(); } -class Class : [|IInterface2|] +class Class : {|CS0535:{|CS0535:IInterface2|}|} { }", @"interface IInterface1 @@ -708,7 +695,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -class Class : [|IInterface1|] +class Class : {|CS0738:IInterface1|} { public int Method1() { @@ -743,7 +730,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(int i); } -class Class : [|IInterface1|] +class Class : {|CS0535:IInterface1|} { public void Method1(string i) { @@ -776,7 +763,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(T t); } -class Class : [|IInterface1|] +class Class : {|CS0535:IInterface1|} { }", @"interface IInterface1 @@ -802,7 +789,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(T t, U u); } -class Class : [|IInterface1|] +class Class : {|CS0535:IInterface1|} { }", @"interface IInterface1 @@ -823,15 +810,17 @@ public void Method1(int t, U u) public async Task TestImplementGenericTypeWithGenericMethodWithNaturalConstraint() { await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface1 +@"using System.Collections.Generic; +interface IInterface1 { void Method1(T t, U u) where U : IList; } -class Class : [|IInterface1|] +class Class : {|CS0535:IInterface1|} { }", -@"interface IInterface1 +@"using System.Collections.Generic; +interface IInterface1 { void Method1(T t, U u) where U : IList; } @@ -854,7 +843,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(T t, U u) where U : T; } -class Class : [|IInterface1|] +class Class : {|CS0535:IInterface1|} { }", @"interface IInterface1 @@ -880,7 +869,7 @@ await TestWithAllCodeStyleOptionsOffAsync( string[] M(); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -906,7 +895,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -class C : [|I|] +class C : {|CS0535:I|} { I i; }", @@ -924,7 +913,7 @@ public void Method1() i.Method1(); } }", -index: 1); +codeAction: ("False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -936,12 +925,12 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -class C : {|FixAllInDocument:I|} +class C : {|CS0535:I|} { I i; } -class D : I +class D : {|CS0535:I|} { I i; }", @@ -969,7 +958,7 @@ public void Method1() i.Method1(); } }", -index: 1); +codeAction: ("False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -981,12 +970,12 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -class C : {|FixAllInDocument:I|} +class C : {|CS0535:I|} { I i; } -class D : I +class D : {|CS0535:I|} { I i { get; } }", @@ -1014,28 +1003,33 @@ public void Method1() i.Method1(); } }", -index: 1); +codeAction: ("False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementThroughFieldMember_FixAll_FieldInOneNonViableInAnother() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + var test = new VerifyCS.Test + { + TestCode = @"interface I { void Method1(); } -class C : {|FixAllInDocument:I|} +class C : {|CS0535:I|} { I i; } -class D : I +class D : {|CS0535:I|} { int i; }", -@"interface I + FixedState = + { + Sources = + { + @"interface I { void Method1(); } @@ -1050,11 +1044,20 @@ public void Method1() } } -class D : I +class D : {|CS0535:I|} { int i; }", -index: 1); + }, + MarkupHandling = MarkupMode.Allow, + }, + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", + CodeActionIndex = 1, + }; + + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -1066,7 +1069,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int this[int x] { get; set; } } -class Goo : [|IGoo|] +class Goo : {|CS0535:IGoo|} { IGoo f; }", @@ -1092,7 +1095,7 @@ public int this[int x] } } }", -index: 1); +codeAction: ("False;False;False:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;f", 1)); } [WorkItem(472, "https://github.com/dotnet/roslyn/issues/472")] @@ -1102,7 +1105,7 @@ public async Task TestImplementThroughFieldMemberRemoveUnnecessaryCast() await TestWithAllCodeStyleOptionsOffAsync( @"using System.Collections; -sealed class X : [|IComparer|] +sealed class X : {|CS0535:IComparer|} { X x; }", @@ -1117,7 +1120,7 @@ public int Compare(object x, object y) return ((IComparer)this.x).Compare(x, y); } }", -index: 1); +codeAction: ("False;False;False:global::System.Collections.IComparer;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;x", 1)); } [WorkItem(472, "https://github.com/dotnet/roslyn/issues/472")] @@ -1127,7 +1130,7 @@ public async Task TestImplementThroughFieldMemberRemoveUnnecessaryCastAndThis() await TestWithAllCodeStyleOptionsOffAsync( @"using System.Collections; -sealed class X : [|IComparer|] +sealed class X : {|CS0535:IComparer|} { X a; }", @@ -1142,7 +1145,7 @@ public int Compare(object x, object y) return ((IComparer)a).Compare(x, y); } }", -index: 1); +codeAction: ("False;False;False:global::System.Collections.IComparer;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -1154,7 +1157,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Method1(); } -abstract class C : [|I|] +abstract class C : {|CS0535:I|} { }", @"interface I @@ -1166,14 +1169,14 @@ abstract class C : I { public abstract void Method1(); }", -index: 1); +codeAction: ("False;True;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementInterfaceWithRefOutParameters() { await TestWithAllCodeStyleOptionsOffAsync( -@"class C : [|I|] +@"class C : {|CS0535:{|CS0535:I|}|} { I goo; } @@ -1203,7 +1206,7 @@ interface I void Method1(ref int x, out int y, int z); int Method2(); }", -index: 1); +codeAction: ("False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;goo", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -1214,10 +1217,11 @@ await TestWithAllCodeStyleOptionsOffAsync( { public int Method1() { + return 0; } } -class C : B, [|I|] +class C : B, {|CS0738:I|} { } @@ -1229,6 +1233,7 @@ interface I { public int Method1() { + return 0; } } @@ -1250,7 +1255,7 @@ interface I public async Task TestConflictingProperties() { await TestWithAllCodeStyleOptionsOffAsync( -@"class Test : [|I1|] +@"class Test : {|CS0737:I1|} { int Prop { get; set; } } @@ -1287,13 +1292,13 @@ interface I1 [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestExplicitProperties() { - await TestMissingInRegularAndScriptAsync( + var code = @"interface I2 { decimal Calc { get; } } -class C : [|I2|] +class C : I2 { protected decimal pay; @@ -1304,7 +1309,9 @@ decimal I2.Calc return pay; } } -}"); +}"; + + await VerifyCS.VerifyCodeFixAsync(code, code); } [WorkItem(539489, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/539489")] @@ -1317,7 +1324,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void @M(); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", @"interface IInterface @@ -1344,7 +1351,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void @int(); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", @"interface IInterface @@ -1371,9 +1378,9 @@ await TestWithAllCodeStyleOptionsOffAsync( void M(); } -class Class : [|@IInterface|] +class Class : {|CS0737:@IInterface|} { - string M(); + string M() => """"; }", @"interface @IInterface { @@ -1382,7 +1389,7 @@ class Class : [|@IInterface|] class Class : @IInterface { - string M(); + string M() => """"; void IInterface.M() { @@ -1401,9 +1408,9 @@ await TestWithAllCodeStyleOptionsOffAsync( void @M(); } -class Class : [|@IInterface|] +class Class : {|CS0737:@IInterface|} { - string M(); + string M() => """"; }", @"interface @IInterface { @@ -1412,7 +1419,7 @@ class Class : [|@IInterface|] class Class : @IInterface { - string M(); + string M() => """"; void IInterface.M() { @@ -1431,9 +1438,9 @@ await TestWithAllCodeStyleOptionsOffAsync( void M(); } -class Class : [|@int|] +class Class : {|CS0737:@int|} { - string M(); + string M() => """"; }", @"interface @int { @@ -1442,7 +1449,7 @@ class Class : [|@int|] class Class : @int { - string M(); + string M() => """"; void @int.M() { @@ -1461,9 +1468,9 @@ await TestWithAllCodeStyleOptionsOffAsync( void @bool(); } -class Class : [|@int|] +class Class : {|CS0737:@int|} { - string @bool(); + string @bool() => """"; }", @"interface @int { @@ -1472,7 +1479,7 @@ class Class : [|@int|] class Class : @int { - string @bool(); + string @bool() => """"; void @int.@bool() { @@ -1490,7 +1497,7 @@ await TestWithAllCodeStyleOptionsOffAsync( { int Prop { get; set; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1523,7 +1530,7 @@ await TestWithAllCodeStyleOptionsOnAsync( int Prop { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1546,7 +1553,7 @@ await TestWithAccessorCodeStyleOptionsOnAsync( int Prop { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1569,7 +1576,7 @@ await TestWithAllCodeStyleOptionsOnAsync( int this[int i] { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1592,7 +1599,7 @@ await TestWithAccessorCodeStyleOptionsOnAsync( int this[int i] { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1615,7 +1622,7 @@ await TestWithAllCodeStyleOptionsOnAsync( int M(); } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1638,7 +1645,7 @@ await TestWithAllCodeStyleOptionsOnAsync( { int Prop { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1661,7 +1668,7 @@ await TestWithAccessorCodeStyleOptionsOnAsync( int Prop { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1685,7 +1692,7 @@ await TestWithAccessorCodeStyleOptionsOnAsync( int Prop { get; set; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1709,7 +1716,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int Prop { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1738,7 +1745,7 @@ await TestWithAllCodeStyleOptionsOnAsync( int this[int i] { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1761,7 +1768,7 @@ await TestWithAllCodeStyleOptionsOnAsync( int this[int i] { get; set; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1784,7 +1791,7 @@ await TestWithAccessorCodeStyleOptionsOnAsync( int this[int i] { get; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1807,7 +1814,7 @@ await TestWithAllCodeStyleOptionsOnAsync( int this[int i] { get; set; } } -public class A : [|DD|] +public class A : {|CS0535:DD|} { }", @"public interface DD @@ -1829,7 +1836,7 @@ await TestWithAllCodeStyleOptionsOffAsync( { void Goo(); } -public class A : [|DD|] +public class A : {|CS0535:DD|} { //comments }", @@ -1853,7 +1860,7 @@ public async Task TestBracePlacement() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|]", +class C : {|CS0535:IServiceProvider|}{|CS1513:|}{|CS1514:|}", @"using System; class C : IServiceProvider { @@ -1869,19 +1876,21 @@ public object GetService(Type serviceType) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestMissingWithIncompleteMember() { - await TestMissingInRegularAndScriptAsync( + var code = @"interface ITest { void Method(); } -class Test : [|ITest|] +class Test : ITest { - p public void Method() + p {|CS1585:public|} void Method() { - throw new NotImplementedException(); + throw new System.NotImplementedException(); } -}"); +}"; + + await VerifyCS.VerifyCodeFixAsync(code, code); } [WorkItem(541380, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/541380")] @@ -1894,7 +1903,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int p { get; set; } } -class c1 : [|i1|] +class c1 : {|CS0535:i1|} { }", @"interface i1 @@ -1917,35 +1926,28 @@ int i1.p } } }", -index: 1); +codeAction: ("True;False;False:global::i1;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(541981, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/541981")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNoDelegateThroughField1() { - await TestActionCountAsync( + var code = @"interface I { void Method1(); } -class C : [|I|] +class C : {|CS0535:I|} { I i { get; set; } -}", -count: 3); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I -{ - void Method1(); -} +}"; -class C : [|I|] -{ - I i { get; set; } -}", -@"interface I + await new VerifyCS.Test + { + TestCode = code, + FixedCode = @"interface I { void Method1(); } @@ -1959,18 +1961,16 @@ public void Method1() throw new System.NotImplementedException(); } }", -index: 0); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I -{ - void Method1(); -} + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + CodeActionEquivalenceKey = "False;False;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); -class C : [|I|] -{ - I i { get; set; } -}", -@"interface I + await new VerifyCS.Test + { + TestCode = code, + FixedCode = @"interface I { void Method1(); } @@ -1984,18 +1984,16 @@ public void Method1() i.Method1(); } }", -index: 1); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I -{ - void Method1(); -} + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + CodeActionEquivalenceKey = "False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", + CodeActionIndex = 1, + }.RunAsync(); -class C : [|I|] -{ - I i { get; set; } -}", -@"interface I + await new VerifyCS.Test + { + TestCode = code, + FixedCode = @"interface I { void Method1(); } @@ -2009,7 +2007,11 @@ void I.Method1() throw new System.NotImplementedException(); } }", -index: 2); + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + CodeActionEquivalenceKey = "True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 2, + }.RunAsync(); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] @@ -2019,7 +2021,7 @@ public async Task TestImplementIReadOnlyListThroughField() await TestWithAllCodeStyleOptionsOffAsync( @"using System.Collections.Generic; -class A : [|IReadOnlyList|] +class A : {|CS0535:{|CS0535:{|CS0535:{|CS0535:IReadOnlyList|}|}|}|} { int[] field; }", @@ -2056,7 +2058,7 @@ IEnumerator IEnumerable.GetEnumerator() return field.GetEnumerator(); } }", -index: 1); +codeAction: ("False;False;False:global::System.Collections.Generic.IReadOnlyList;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;field", 1)); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] @@ -2066,7 +2068,7 @@ public async Task TestImplementIReadOnlyListThroughProperty() await TestWithAllCodeStyleOptionsOffAsync( @"using System.Collections.Generic; -class A : [|IReadOnlyList|] +class A : {|CS0535:{|CS0535:{|CS0535:{|CS0535:IReadOnlyList|}|}|}|} { int[] field { get; set; } }", @@ -2103,7 +2105,7 @@ IEnumerator IEnumerable.GetEnumerator() return field.GetEnumerator(); } }", -index: 1); +codeAction: ("False;False;False:global::System.Collections.Generic.IReadOnlyList;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;field", 1)); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] @@ -2124,7 +2126,7 @@ int I.M() } } -class B : [|I|] +class B : {|CS0535:I|} { A a; }", @@ -2150,51 +2152,25 @@ public int M() return ((I)a).M(); } }", -index: 1); +codeAction: ("False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", 1)); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementInterfaceThroughField_FieldImplementsMultipleInterfaces() { - await TestActionCountAsync( -@"interface I + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } interface I2 { - int M2() } - -class A : I, I2 -{ - int I.M() - { - return 0; - } - - int I2.M2() - { - return 0; - } -} - -class B : [|I|], I2 -{ - A a; -}", -count: 3); - await TestActionCountAsync( -@"interface I -{ - int M(); + int M2(); } -interface I2 -{ - int M2() } - class A : I, I2 { int I.M() @@ -2208,47 +2184,24 @@ int I2.M2() } } -class B : I, [|I2|] +class B : {|CS0535:I|}, {|CS0535:I2|} { A a; }", -count: 3); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + FixedState = + { + Sources = + { + @"interface I { int M(); } interface I2 { - int M2() } - -class A : I, I2 -{ - int I.M() - { - return 0; - } - - int I2.M2() - { - return 0; - } -} - -class B : [|I|], I2 -{ - A a; -}", -@"interface I -{ - int M(); + int M2(); } -interface I2 -{ - int M2() } - class A : I, I2 { int I.M() @@ -2262,7 +2215,7 @@ int I2.M2() } } -class B : I, I2 +class B : I, {|CS0535:I2|} { A a; @@ -2271,16 +2224,28 @@ public int M() return ((I)a).M(); } }", -index: 1); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + DiagnosticSelector = diagnostics => diagnostics[0], + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", + CodeActionIndex = 1, + }.RunAsync(); + + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } interface I2 { - int M2() } + int M2(); +} class A : I, I2 { @@ -2295,18 +2260,23 @@ int I2.M2() } } -class B : I, [|I2|] +class B : {|CS0535:I|}, {|CS0535:I2|} { A a; }", -@"interface I + FixedState = + { + Sources = + { + @"interface I { int M(); } interface I2 { - int M2() } + int M2(); +} class A : I, I2 { @@ -2321,7 +2291,7 @@ int I2.M2() } } -class B : I, I2 +class B : {|CS0535:I|}, I2 { A a; @@ -2330,35 +2300,25 @@ public int M2() return ((I2)a).M2(); } }", -index: 1); + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + DiagnosticSelector = diagnostics => diagnostics[1], + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "False;False;False:global::I2;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", + CodeActionIndex = 1, + }.RunAsync(); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementInterfaceThroughField_MultipleFieldsCanImplementInterface() { - await TestActionCountAsync( -@"interface I -{ - int M(); -} - -class A : I -{ - int I.M() - { - return 0; - } -} - -class B : [|I|] -{ - A a; - A aa; -}", -count: 4); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } @@ -2371,12 +2331,16 @@ int I.M() } } -class B : [|I|] +class B : {|CS0535:I|} { A a; A aa; }", -@"interface I + FixedState = + { + Sources = + { + @"interface I { int M(); } @@ -2399,9 +2363,18 @@ public int M() return ((I)a).M(); } }", -index: 1); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(4, codeActions.Length), + CodeActionEquivalenceKey = "False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", + CodeActionIndex = 1, + }.RunAsync(); + + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } @@ -2414,12 +2387,16 @@ int I.M() } } -class B : [|I|] +class B : {|CS0535:I|} { A a; A aa; }", -@"interface I + FixedState = + { + Sources = + { + @"interface I { int M(); } @@ -2442,22 +2419,31 @@ public int M() return ((I)aa).M(); } }", -index: 2); + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(4, codeActions.Length), + CodeActionEquivalenceKey = "False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;aa", + CodeActionIndex = 2, + }.RunAsync(); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementInterfaceThroughField_MultipleFieldsForMultipleInterfaces() { - await TestActionCountAsync( -@"interface I + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } interface I2 { - int M2() } + int M2(); +} class A : I { @@ -2475,21 +2461,24 @@ int I2.M2() } } -class C : [|I|], I2 +class C : {|CS0535:I|}, {|CS0535:I2|} { A a; B b; }", -count: 3); - await TestActionCountAsync( -@"interface I + FixedState = + { + Sources = + { + @"interface I { int M(); } interface I2 { - int M2() } + int M2(); +} class A : I { @@ -2507,21 +2496,38 @@ int I2.M2() } } -class C : I, [|I2|] +class C : I, {|CS0535:I2|} { A a; B b; + + public int M() + { + return ((I)a).M(); + } }", -count: 3); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + DiagnosticSelector = diagnostics => diagnostics[0], + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "False;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", + CodeActionIndex = 1, + }.RunAsync(); + + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } interface I2 { - int M2() } + int M2(); +} class A : I { @@ -2539,19 +2545,24 @@ int I2.M2() } } -class C : [|I|], I2 +class C : {|CS0535:I|}, {|CS0535:I2|} { A a; B b; }", -@"interface I + FixedState = + { + Sources = + { + @"interface I { int M(); } interface I2 { - int M2() } + int M2(); +} class A : I { @@ -2569,57 +2580,81 @@ int I2.M2() } } -class C : I, I2 +class C : {|CS0535:I|}, I2 { A a; B b; - public int M() + public int M2() { - return ((I)a).M(); + return ((I2)b).M2(); } }", -index: 1); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + DiagnosticSelector = diagnostics => diagnostics[1], + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "False;False;False:global::I2;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;b", + CodeActionIndex = 1, + }.RunAsync(); + } + + [WorkItem(18556, "https://github.com/dotnet/roslyn/issues/18556")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestImplementInterfaceThroughExplicitProperty() + { + await new VerifyCS.Test + { + TestCode = @"interface IA +{ + IB B { get; } +} +interface IB { int M(); } - -interface I2 +class AB : IA, {|CS0535:IB|} { - int M2() } - -class A : I + IB IA.B => null; +}", + FixedCode = @"interface IA { - int I.M() - { - return 0; - } + IB B { get; } } - -class B : I2 +interface IB { - int I2.M2() - { - return 0; - } + int M(); } - -class C : I, [|I2|] +class AB : IA, IB { - A a; - B b; + IB IA.B => null; + + public int M() + { + return ((IA)this).B.M(); + } }", -@"interface I + Options = { AllOptionsOff }, + CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), + CodeActionEquivalenceKey = "False;False;False:global::IB;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;IA.B", + CodeActionIndex = 1, + }.RunAsync(); + } + + [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestNoImplementThroughIndexer() + { + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } -interface I2 -{ - int M2() } - class A : I { int I.M() @@ -2628,83 +2663,55 @@ int I.M() } } -class B : I2 -{ - int I2.M2() - { - return 0; - } -} - -class C : I, I2 +class B : {|CS0535:I|} { - A a; - B b; - - public int M2() + A this[int index] { - return ((I2)b).M2(); - } -}", -index: 1); - } - - [WorkItem(18556, "https://github.com/dotnet/roslyn/issues/18556")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] - public async Task TestImplementInterfaceThroughExplicitProperty() + get { - await TestActionCountAsync( -@"interface IA -{ - IB B { get; } -} -interface IB -{ - int M(); -} -class AB : IA, [|IB|] -{ - IB IA.B => null; + return null; + } + } }", -count: 3); - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IA -{ - IB B { get; } -} -interface IB + FixedCode = @"interface I { int M(); } -class AB : IA, [|IB|] -{ - IB IA.B => null; -}", -@"interface IA -{ - IB B { get; } -} -interface IB + +class A : I { - int M(); + int I.M() + { + return 0; + } } -class AB : IA, [|IB|] + +class B : I { - IB IA.B => null; + A this[int index] + { + get + { + return null; + } + } public int M() { - return ((IA)this).B.M(); + throw new System.NotImplementedException(); } -}", index: 1); +}", + CodeActionsVerifier = codeActions => Assert.Equal(2, codeActions.Length), + }.RunAsync(); } [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] - public async Task TestNoImplementThroughIndexer() + public async Task TestNoImplementThroughWriteOnlyProperty() { - await TestActionCountAsync( -@"interface I + await new VerifyCS.Test + { + TestCode = @"interface I { int M(); } @@ -2717,25 +2724,16 @@ int I.M() } } -class B : [|I|] +class B : {|CS0535:I|} { - A this[int index] + A a { - get + set { - return null; } - }; + } }", -count: 2); - } - - [WorkItem(768799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/768799")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] - public async Task TestNoImplementThroughWriteOnlyProperty() - { - await TestActionCountAsync( -@"interface I + FixedCode = @"interface I { int M(); } @@ -2748,7 +2746,7 @@ int I.M() } } -class B : [|I|] +class B : {|CS0535:I|} { A a { @@ -2756,8 +2754,14 @@ A a { } } + + public int M() + { + throw new System.NotImplementedException(); + } }", -count: 2); + CodeActionsVerifier = codeActions => Assert.Equal(2, codeActions.Length), + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -2771,10 +2775,10 @@ interface IGoo class CanGoo : IGoo { - public event EventHandler E; + public event System.EventHandler E; } -class HasCanGoo : [|IGoo|] +class HasCanGoo : {|CS0535:IGoo|} { CanGoo canGoo; }", @@ -2788,7 +2792,7 @@ interface IGoo class CanGoo : IGoo { - public event EventHandler E; + public event System.EventHandler E; } class HasCanGoo : IGoo @@ -2807,17 +2811,17 @@ public event EventHandler E ((IGoo)canGoo).E -= value; } } -}", index: 1); +}", codeAction: ("False;False;False:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;canGoo", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementEventThroughExplicitMember() { await TestInRegularAndScriptAsync( -@"interface IGoo { event System . EventHandler E ; } class CanGoo : IGoo { event IGoo.EventHandler E; } class HasCanGoo : [|IGoo|] { CanGoo canGoo; } ", +@"interface IGoo { event System . EventHandler E ; } class CanGoo : IGoo { event System.EventHandler IGoo.E { add { } remove { } } } class HasCanGoo : {|CS0535:IGoo|} { CanGoo canGoo; } ", @"using System; -interface IGoo { event System . EventHandler E ; } class CanGoo : IGoo { event IGoo.EventHandler E; } class HasCanGoo : IGoo { CanGoo canGoo; +interface IGoo { event System . EventHandler E ; } class CanGoo : IGoo { event System.EventHandler IGoo.E { add { } remove { } } } class HasCanGoo : IGoo { CanGoo canGoo; public event EventHandler E { @@ -2832,7 +2836,7 @@ public event EventHandler E } } } ", -index: 1); +codeAction: ("False;False;False:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;canGoo", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -2844,7 +2848,7 @@ await TestWithAllCodeStyleOptionsOffAsync( event System.EventHandler E; } -abstract class Goo : [|IGoo|] +abstract class Goo : {|CS0535:IGoo|} { }", @"using System; @@ -2858,7 +2862,7 @@ abstract class Goo : IGoo { public event EventHandler E; }", -index: 0); +codeAction: ("False;False;True:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 0)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -2870,7 +2874,7 @@ await TestWithAllCodeStyleOptionsOffAsync( event System.EventHandler E; } -abstract class Goo : [|IGoo|] +abstract class Goo : {|CS0535:IGoo|} { }", @"using System; @@ -2884,7 +2888,7 @@ abstract class Goo : IGoo { public abstract event EventHandler E; }", -index: 1); +codeAction: ("False;True;True:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -2896,7 +2900,7 @@ await TestWithAllCodeStyleOptionsOffAsync( event System.EventHandler E; } -abstract class Goo : [|IGoo|] +abstract class Goo : {|CS0535:IGoo|} { }", @"using System; @@ -2921,44 +2925,49 @@ event EventHandler IGoo.E } } }", -index: 2); +codeAction: ("True;False;False:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 2)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestFaultToleranceInStaticMembers_01() { - await TestMissingAsync( -@"interface IFoo + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IFoo { static string Name { set; get; } - static int Foo(string s); + static int {|CS0501:Foo|}(string s); } -class Program : [|IFoo|] +class Program : IFoo { -}"); +}", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestFaultToleranceInStaticMembers_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IFoo + var test = new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IFoo { string Name { set; get; } - static int Foo(string s); + static int {|CS0501:Foo|}(string s); } -class Program : [|IFoo|] +class Program : {|CS0535:IFoo|} { }", -@"interface IFoo + FixedCode = @"interface IFoo { string Name { set; get; } - static int Foo(string s); + static int {|CS0501:Foo|}(string s); } class Program : IFoo @@ -2975,24 +2984,30 @@ public string Name throw new System.NotImplementedException(); } } -}"); +}", + }; + + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestFaultToleranceInStaticMembers_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IGoo + var test = new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IGoo { static string Name { set; get; } int Goo(string s); } -class Program : [|IGoo|] +class Program : {|CS0535:IGoo|} { }", -@"interface IGoo + FixedCode = @"interface IGoo { static string Name { set; get; } @@ -3005,7 +3020,11 @@ public int Goo(string s) { throw new System.NotImplementedException(); } -}"); +}", + }; + + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -3017,7 +3036,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int this[int index] { get; set; } } -class IndexerClass : [|ISomeInterface|] +class IndexerClass : {|CS0535:ISomeInterface|} { }", @"public interface ISomeInterface @@ -3051,7 +3070,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int this[int index] { get; set; } } -class IndexerClass : [|ISomeInterface|] +class IndexerClass : {|CS0535:ISomeInterface|} { }", @"public interface ISomeInterface @@ -3074,7 +3093,7 @@ int ISomeInterface.this[int index] } } }", -index: 1); +codeAction: ("True;False;False:global::ISomeInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -3086,7 +3105,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int this[int index] { get; } } -class IndexerClass : [|ISomeInterface|] +class IndexerClass : {|CS0535:ISomeInterface|} { }", @"public interface ISomeInterface @@ -3116,7 +3135,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo() where T : class; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"interface I @@ -3143,7 +3162,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo() where T : class; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"interface I @@ -3158,7 +3177,7 @@ void I.Goo() throw new System.NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(542357, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542357")] @@ -3171,7 +3190,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo() where T : System.Attribute; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3200,7 +3219,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int this[int x] { get; set; } } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -3237,7 +3256,7 @@ interface I void Goo() where T : IComparable; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3268,7 +3287,7 @@ interface I void Goo() where T : IComparable; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3285,7 +3304,7 @@ void I.Goo() throw new NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(542587, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542587")] @@ -3298,7 +3317,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"interface I @@ -3325,7 +3344,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"interface I @@ -3352,7 +3371,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"interface I @@ -3367,7 +3386,7 @@ void I.Goo() throw new System.NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(542587, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542587")] @@ -3382,7 +3401,7 @@ interface I void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3413,7 +3432,7 @@ interface I void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3446,7 +3465,7 @@ interface I delegate void Bar(); -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3479,7 +3498,7 @@ interface I void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3510,7 +3529,7 @@ interface I void Goo() where T : class, S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3545,7 +3564,7 @@ enum E { } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3561,7 +3580,7 @@ enum E class A : I { - void I.Goo() + void I.Goo<{|CS0455:T|}>() { throw new NotImplementedException(); } @@ -3580,7 +3599,7 @@ interface I void Goo() where T : S; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3611,7 +3630,7 @@ interface I void Goo() where T : S; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3642,7 +3661,7 @@ interface I void Goo() where T : Exception, S; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3654,7 +3673,7 @@ interface I class C : I { - void I.Goo() + void I.Goo<{|CS0455:T|}>() { throw new NotImplementedException(); } @@ -3673,7 +3692,7 @@ interface I void Goo() where T : class, S; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3705,7 +3724,7 @@ interface I void Goo(T x, IList list) where S : T; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3738,7 +3757,7 @@ interface I void Goo(T x, IList list) where S : T; } -class A : [|I|] +class A : {|CS0535:I|} { }", @"using System; @@ -3756,7 +3775,7 @@ void I.Goo(S x, IList list) throw new NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(542505, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542505")] @@ -3774,7 +3793,7 @@ void Goo(X x, Y y, IList list1, IList list2) where B : IList; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3813,7 +3832,7 @@ void Goo(X x, Y y, IList list1, IList list2) where B : IList; } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -3833,7 +3852,7 @@ void I.Goo(A x, B y, IList list1, IList list2) throw new NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(542506, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542506")] @@ -3854,7 +3873,7 @@ interface I void Goo(B x); } - class C : [|I|] + class C : {|CS0535:I|} { } }", @@ -3897,7 +3916,7 @@ interface I void Goo(B[] x); } - class C : [|I|] + class C : {|CS0535:I|} { } }", @@ -3938,7 +3957,7 @@ interface I void Goo(B[][,][,,][,,,] x); } - class C : [|I|] + class C : {|CS0535:I|} { } }", @@ -3973,7 +3992,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int Gibberish { get; set; } } -abstract class Goo : [|IGoo|] +abstract class Goo : {|CS0535:IGoo|} { }", @"interface IGoo @@ -3985,22 +4004,24 @@ abstract class Goo : IGoo { public abstract int Gibberish { get; set; } }", -index: 1); +codeAction: ("False;True;True:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(544210, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544210")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestMissingOnWrongArity() { - await TestMissingInRegularAndScriptAsync( + var code = @"interface I1 { int X { get; set; } } -class C : [|I1|] +class C : {|CS0305:I1|} { -}"); +}"; + + await VerifyCS.VerifyCodeFixAsync(code, code); } [WorkItem(544281, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544281")] @@ -4013,7 +4034,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int Goo(int g = 0); } -class Opt : [|IOptional|] +class Opt : {|CS0535:IOptional|} { }", @"interface IOptional @@ -4040,7 +4061,7 @@ await TestWithAllCodeStyleOptionsOffAsync( int Goo(int g = 0); } -class Opt : [|IOptional|] +class Opt : {|CS0535:IOptional|} { }", @"interface IOptional @@ -4055,20 +4076,22 @@ int IOptional.Goo(int g) throw new System.NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::IOptional;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestMissingInHiddenType() { - await TestMissingInRegularAndScriptAsync( + var code = @"using System; -class Program : [|IComparable|] +class Program : {|CS0535:IComparable|} { #line hidden } -#line default"); +#line default"; + + await VerifyCS.VerifyCodeFixAsync(code, code); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -4078,7 +4101,7 @@ await TestWithAllCodeStyleOptionsOffAsync( @"#line default using System; -partial class Program : [|IComparable|] +partial class Program : {|CS0535:IComparable|} { void Goo() { @@ -4110,7 +4133,7 @@ public async Task TestGenerateIfAvailableRegionExists() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -partial class Program : [|IComparable|] +partial class Program : {|CS0535:IComparable|} { #line hidden } @@ -4140,23 +4163,26 @@ public int CompareTo(object obj) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNoGenerateInVenusCase1() { - await TestMissingInRegularAndScriptAsync( + var code = @"using System; #line 1 ""Bar"" -class Goo : [|IComparable|] +class Goo : {|CS0535:IComparable|}{|CS1513:|}{|CS1514:|} #line default #line hidden -// stuff"); +// stuff"; + + await VerifyCS.VerifyCodeFixAsync(code, code); } [WorkItem(545476, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545476")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestOptionalDateTime1() { - await TestWithAllCodeStyleOptionsOffAsync( -@"using System; + await new VerifyCS.Test + { + TestCode = @"using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -4165,10 +4191,10 @@ interface IGoo void Goo([Optional][DateTimeConstant(100)] DateTime x); } -public class C : [|IGoo|] +public class C : {|CS0535:IGoo|} { }", -@"using System; + FixedCode = @"using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -4183,7 +4209,12 @@ public void Goo([DateTimeConstant(100), Optional] DateTime x) { throw new NotImplementedException(); } -}"); +}", + Options = { AllOptionsOff }, + + // 🐛 one value is generated with 0L instead of 0 + CodeActionValidationMode = CodeActionValidationMode.None, + }.RunAsync(); } [WorkItem(545476, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545476")] @@ -4200,7 +4231,7 @@ interface IGoo void Goo([Optional][DateTimeConstant(100)] DateTime x); } -public class C : [|IGoo|] +public class C : {|CS0535:IGoo|} { }", @"using System; @@ -4219,7 +4250,7 @@ void IGoo.Goo(DateTime x) throw new NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(545477, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545477")] @@ -4236,7 +4267,7 @@ interface IGoo void Goo2([Optional][IDispatchConstant] object x); } -public class C : [|IGoo|] +public class C : {|CS0535:{|CS0535:IGoo|}|} { }", @"using System.Runtime.CompilerServices; @@ -4276,7 +4307,7 @@ interface IGoo void Goo2([Optional][IDispatchConstant] object x); } -public class C : [|IGoo|] +public class C : {|CS0535:{|CS0535:IGoo|}|} { }", @"using System.Runtime.CompilerServices; @@ -4300,7 +4331,7 @@ void IGoo.Goo2(object x) throw new System.NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::IGoo;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(545464, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545464")] @@ -4313,7 +4344,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(); } -public class Goo : [|IGoo|] +public class Goo : {|CS0535:IGoo|} { }", @"interface IGoo @@ -4334,7 +4365,7 @@ void IGoo.Goo() public async Task TestStringLiteral() { await TestWithAllCodeStyleOptionsOffAsync( -@"interface IGoo { void Goo ( string s = ""\"""" ) ; } class B : [|IGoo|] { } ", +@"interface IGoo { void Goo ( string s = ""\"""" ) ; } class B : {|CS0535:IGoo|} { } ", @"interface IGoo { void Goo ( string s = ""\"""" ) ; } class B : IGoo { @@ -4359,7 +4390,7 @@ interface d void m(b? x = null, b? y = default(b?)); } -class c : [|d|] +class c : {|CS0535:d|} { }", @"struct b @@ -4394,7 +4425,7 @@ interface d void m(b? x = null, b? y = default(b?)); } -class c : [|d|] +class c : {|CS0535:d|} { }", @"struct b @@ -4412,7 +4443,7 @@ void d.m(b? x, b? y) { throw new System.NotImplementedException(); } -}", 1); +}", codeAction: ("True;False;False:global::d;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(916114, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/916114")] @@ -4425,7 +4456,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void m(int? x = 5, int? y = null); } -class c : [|d|] +class c : {|CS0535:d|} { }", @"interface d @@ -4454,7 +4485,7 @@ interface I void Goo([Optional] I o); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Runtime.InteropServices; @@ -4476,8 +4507,9 @@ public void Goo([Optional] I o) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestIntegralAndFloatLiterals() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface I + await new VerifyCS.Test + { + TestCode = @"interface I { void M01(short s = short.MinValue); void M02(short s = -1); @@ -4505,10 +4537,10 @@ await TestWithAllCodeStyleOptionsOffAsync( void M24(double s = double.MaxValue); } -class C : [|I|] +class C : {|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:I|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|}|} { }", -@"interface I + FixedCode = @"interface I { void M01(short s = short.MinValue); void M02(short s = -1); @@ -4657,7 +4689,12 @@ public void M24(double s = double.MaxValue) { throw new System.NotImplementedException(); } -}"); +}", + Options = { AllOptionsOff }, + + // 🐛 one value is generated with 0U instead of 0 + CodeActionValidationMode = CodeActionValidationMode.None, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -4685,7 +4722,7 @@ interface I void M2(FlagE e = FlagE.A | FlagE.B); } -class C : [|I|] +class C : {|CS0535:{|CS0535:I|}|} { }", @"using System; @@ -4744,7 +4781,7 @@ interface I void M11(char c = '\u2029'); } -class C : [|I|] +class C : {|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:I|}|}|}|}|}|}|}|}|}|}|} { }", @"using System; @@ -4835,7 +4872,7 @@ interface I void Goo(DayOfWeek x = DayOfWeek.Friday); } -class C : [|I|] +class C : {|CS0535:I|} { DayOfWeek DayOfWeek { get; set; } }", @@ -4867,7 +4904,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(decimal x = decimal.MaxValue); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -4894,7 +4931,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(decimal? x = decimal.MaxValue); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -4923,7 +4960,7 @@ interface I void Goo(DayOfWeek? x = DayOfWeek.Friday); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -4952,7 +4989,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(byte x = 1); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -4980,7 +5017,7 @@ interface I void Goo(ConsoleColor x = (ConsoleColor)(-1)); } -class C : [|I|] +class C : {|CS0535:I|} { }"; @@ -5014,7 +5051,7 @@ interface I void Goo(ConsoleColor x = (ConsoleColor)int.MaxValue); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System; @@ -5048,7 +5085,7 @@ interface I void Goo(E x = 0); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"enum E @@ -5082,7 +5119,7 @@ interface I void Goo([Optional][DefaultParameterValue(1)] int x, int[,] y); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Runtime.InteropServices; @@ -5094,7 +5131,7 @@ interface I class C : I { - public void Goo([DefaultParameterValue(1), Optional] int x = 1, int[,] y = null) + public void Goo([{|CS1745:DefaultParameterValue|}(1), {|CS1745:Optional|}] int x = {|CS8017:1|}, int[,] y = null) { throw new System.NotImplementedException(); } @@ -5113,7 +5150,7 @@ interface I void Goo([Optional, DefaultParameterValue(1)] int x, int[] y, int[] z); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Runtime.InteropServices; @@ -5125,7 +5162,7 @@ interface I class C : I { - public void Goo([DefaultParameterValue(1), Optional] int x = 1, int[] y = null, int[] z = null) + public void Goo([{|CS1745:DefaultParameterValue|}(1), {|CS1745:Optional|}] int x = {|CS8017:1|}, int[] y = null, int[] z = null) { throw new System.NotImplementedException(); } @@ -5136,7 +5173,9 @@ public void Goo([DefaultParameterValue(1), Optional] int x = 1, int[] y = null, [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestAttributeInParameter() { - await TestWithAllCodeStyleOptionsOffAsync( + var test = new VerifyCS.Test + { + TestCode = @"using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -5145,10 +5184,11 @@ interface I { void Goo([Optional][DateTimeConstant(100)] DateTime d1, [Optional][IUnknownConstant] object d2); } -class C : [|I|] +class C : {|CS0535:I|} { } ", + FixedCode = @"using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -5164,7 +5204,13 @@ public void Goo([DateTimeConstant(100), Optional] DateTime d1, [IUnknownConstant throw new NotImplementedException(); } } -"); +", + // 🐛 the DateTimeConstant attribute is generated with 100L instead of 100 + CodeActionValidationMode = CodeActionValidationMode.None, + }; + + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [WorkItem(545897, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545897")] @@ -5177,7 +5223,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void T1(S x, T y); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -5206,7 +5252,7 @@ interface I void Goo(S y, List.Enumerator x); } -class D : [|I|] +class D : {|CS0535:I|} { }", @"using System.Collections.Generic; @@ -5235,7 +5281,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(float x = 1E10F); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -5262,7 +5308,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo<@class>(); } -class C : [|I|]", +class C : {|CS0535:I|}{|CS1513:|}{|CS1514:|}", @"interface I { void Goo<@class>(); @@ -5289,7 +5335,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo2(decimal x = -1E28M); } -class C : [|I|] +class C : {|CS0535:{|CS0535:I|}|} { }", @"interface I @@ -5322,7 +5368,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(decimal x = 0.1M); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -5347,8 +5393,8 @@ await TestWithAllCodeStyleOptionsOffAsync( @"using System; // Implement interface -class C : [|IServiceProvider|] /* -", +class C : {|CS0535:IServiceProvider|} {|CS1035:|}/* +{|CS1513:|}{|CS1514:|}", @"using System; // Implement interface @@ -5371,7 +5417,7 @@ await TestWithAllCodeStyleOptionsOffAsync( @"using System; // Implement interface -class C : [|IServiceProvider|] +class C : {|CS0535:IServiceProvider|}{|CS1513:|}{|CS1514:|} #pragma warning disable ", @"using System; @@ -5395,7 +5441,7 @@ public async Task TestCommentAfterInterfaceList1() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|] // Implement interface +class C : {|CS0535:IServiceProvider|}{|CS1513:|}{|CS1514:|} // Implement interface ", @"using System; @@ -5416,7 +5462,7 @@ public async Task TestCommentAfterInterfaceList2() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|] +class C : {|CS0535:IServiceProvider|}{|CS1513:|}{|CS1514:|} // Implement interface ", @"using System; @@ -5439,7 +5485,7 @@ public async Task TestImplementIDisposable_NoDisposePattern() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IDisposable|]", +class C : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}", @"using System; class C : IDisposable { @@ -5448,7 +5494,7 @@ public void Dispose() throw new NotImplementedException(); } } -", index: 0); +", codeAction: ("False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 0)); } [WorkItem(994456, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994456")] @@ -5458,7 +5504,7 @@ public async Task TestImplementIDisposable_DisposePattern() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IDisposable|]", +class C : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}", $@"using System; class C : IDisposable {{ @@ -5466,7 +5512,7 @@ class C : IDisposable {DisposePattern("protected virtual ", "C", "public void ")} }} -", index: 1); +", codeAction: ("False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 1)); } [WorkItem(994456, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994456")] @@ -5476,7 +5522,7 @@ public async Task TestImplementIDisposableExplicitly_NoDisposePattern() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IDisposable|]", +class C : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}", @"using System; class C : IDisposable { @@ -5485,7 +5531,7 @@ void IDisposable.Dispose() throw new NotImplementedException(); } } -", index: 2); +", codeAction: ("True;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 2)); } [WorkItem(994456, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994456")] @@ -5495,7 +5541,7 @@ public async Task TestImplementIDisposableExplicitly_DisposePattern() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|System.IDisposable|] +class C : {|CS0535:System.IDisposable|} { class IDisposable { @@ -5511,7 +5557,7 @@ class IDisposable }} {DisposePattern("protected virtual ", "C", "void System.IDisposable.")} -}}", index: 3); +}}", codeAction: ("True;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 3)); } [WorkItem(994456, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994456")] @@ -5521,13 +5567,13 @@ public async Task TestImplementIDisposableAbstractly_NoDisposePattern() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -abstract class C : [|IDisposable|]", +abstract class C : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}", @"using System; abstract class C : IDisposable { public abstract void Dispose(); } -", index: 2); +", codeAction: ("False;True;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 2)); } [WorkItem(994456, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994456")] @@ -5537,7 +5583,7 @@ public async Task TestImplementIDisposableThroughMember_NoDisposePattern() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IDisposable|] +class C : {|CS0535:IDisposable|} { private IDisposable goo; }", @@ -5550,22 +5596,29 @@ public void Dispose() { goo.Dispose(); } -}", index: 2); +}", codeAction: ("False;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;goo", 2)); } [WorkItem(941469, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/941469")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementIDisposableExplicitly_NoNamespaceImportForSystem() { - await TestWithAllCodeStyleOptionsOffAsync( -@"class C : [|System.IDisposable|]", -$@"class C : System.IDisposable + await new VerifyCS.Test + { + TestCode = @"class C : {|CS0535:System.IDisposable|}{|CS1513:|}{|CS1514:|}", + FixedCode = $@"class C : System.IDisposable {{ private bool disposedValue; {DisposePattern("protected virtual ", "C", "void System.IDisposable.", gcPrefix: "System.")} }} -", index: 3); +", + CodeActionEquivalenceKey = "True;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", + CodeActionIndex = 3, + + // 🐛 generated QualifiedName where SimpleMemberAccessExpression was expected + CodeActionValidationMode = CodeActionValidationMode.None, + }.RunAsync(); } [WorkItem(951968, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/951968")] @@ -5578,7 +5631,7 @@ interface I : IDisposable { void F(); } -class C : [|I|] +class C : {|CS0535:{|CS0535:I|}|} { }", @"using System; @@ -5597,7 +5650,7 @@ public void F() { throw new NotImplementedException(); } -}", index: 0); +}", codeAction: ("False;False;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 0)); } [WorkItem(951968, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/951968")] @@ -5610,7 +5663,7 @@ interface I : IDisposable { void F(); } -class C : [|I|] +class C : {|CS0535:{|CS0535:I|}|} { }", $@"using System; @@ -5628,7 +5681,7 @@ public void F() }} {DisposePattern("protected virtual ", "C", "public void ")} -}}", index: 1); +}}", codeAction: ("False;False;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 1)); } [WorkItem(951968, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/951968")] @@ -5641,7 +5694,7 @@ interface I : IDisposable { void F(); } -class C : [|I|] +class C : {|CS0535:{|CS0535:I|}|} { }", $@"using System; @@ -5659,7 +5712,7 @@ void I.F() }} {DisposePattern("protected virtual ", "C", "void IDisposable.")} -}}", index: 3); +}}", codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 3)); } [WorkItem(941469, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/941469")] @@ -5674,7 +5727,7 @@ interface IDisposable void Dispose(); } - class C : [|IDisposable|] + class C : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|} }", @"namespace System { @@ -5690,7 +5743,7 @@ void IDisposable.Dispose() throw new NotImplementedException(); } } -}", index: 1); +}", codeAction: ("True;False;False:global::System.IDisposable;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -5698,7 +5751,7 @@ public async Task TestDontImplementDisposePatternForStructures1() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -struct S : [|IDisposable|]", +struct S : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}", @"using System; struct S : IDisposable { @@ -5715,7 +5768,7 @@ public async Task TestDontImplementDisposePatternForStructures2() { await TestWithAllCodeStyleOptionsOffAsync( @"using System; -struct S : [|IDisposable|]", +struct S : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}", @"using System; struct S : IDisposable { @@ -5724,15 +5777,16 @@ void IDisposable.Dispose() throw new NotImplementedException(); } } -", index: 1); +", codeAction: ("True;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(545924, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545924")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestEnumNestedInGeneric() { - await TestWithAllCodeStyleOptionsOffAsync( -@"class C + var test = new VerifyCS.Test() + { + TestCode = @"class C { public enum E { @@ -5745,10 +5799,10 @@ interface I void Goo(C.E x = C.E.X); } -class D : [|I|] +class D : {|CS0535:I|} { }", -@"class C + FixedCode = @"class C { public enum E { @@ -5767,7 +5821,13 @@ public void Goo(C.E x = C.E.X) { throw new System.NotImplementedException(); } -}"); +}", + // 🐛 generated QualifiedName where SimpleMemberAccessExpression was expected + CodeActionValidationMode = CodeActionValidationMode.None, + }; + + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [WorkItem(545939, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545939")] @@ -5777,10 +5837,10 @@ public async Task TestUnterminatedString1() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|] @""", +class C : {|CS0535:IServiceProvider|} {|CS1039:|}@""{|CS1513:|}{|CS1514:|}", @"using System; -class C : IServiceProvider @""""{ +class C : IServiceProvider {|CS1003:@""""|}{ public object GetService(Type serviceType) { throw new NotImplementedException(); @@ -5796,10 +5856,10 @@ public async Task TestUnterminatedString2() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|] """, +class C : {|CS0535:IServiceProvider|} {|CS1010:|}""{|CS1513:|}{|CS1514:|}", @"using System; -class C : IServiceProvider """"{ +class C : IServiceProvider {|CS1003:""""|}{ public object GetService(Type serviceType) { throw new NotImplementedException(); @@ -5815,10 +5875,10 @@ public async Task TestUnterminatedString3() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|] @""", +class C : {|CS0535:IServiceProvider|} {|CS1039:|}@""{|CS1513:|}{|CS1514:|}", @"using System; -class C : IServiceProvider @""""{ +class C : IServiceProvider {|CS1003:@""""|}{ public object GetService(Type serviceType) { throw new NotImplementedException(); @@ -5834,10 +5894,10 @@ public async Task TestUnterminatedString4() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class C : [|IServiceProvider|] """, +class C : {|CS0535:IServiceProvider|} {|CS1010:|}""{|CS1513:|}{|CS1514:|}", @"using System; -class C : IServiceProvider """"{ +class C : IServiceProvider {|CS1003:""""|}{ public object GetService(Type serviceType) { throw new NotImplementedException(); @@ -5859,7 +5919,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo4(decimal x = -1E-24M); } -class C : [|I|] +class C : {|CS0535:{|CS0535:{|CS0535:{|CS0535:I|}|}|}|} { }", @"interface I @@ -5898,8 +5958,9 @@ public void Goo4(decimal x = -0.000000000000000000000001M) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestGenericEnumWithRenamedTypeParameters() { - await TestWithAllCodeStyleOptionsOffAsync( -@"class C + var test = new VerifyCS.Test + { + TestCode = @"class C { public enum E { @@ -5912,10 +5973,10 @@ interface I void Goo(S y, C.E x = C.E.X); } -class D : [|I|] +class D : {|CS0535:I|} { }", -@"class C + FixedCode = @"class C { public enum E { @@ -5934,7 +5995,13 @@ public void Goo(T y, C.E x = C.E.X) { throw new System.NotImplementedException(); } -}"); +}", + // 🐛 generated QualifiedName where SimpleMemberAccessExpression was expected + CodeActionValidationMode = CodeActionValidationMode.None, + }; + + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [WorkItem(545919, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545919")] @@ -5947,7 +6014,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(S T1); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -5977,7 +6044,7 @@ interface I bool Goo([MarshalAs(UnmanagedType.U1)] bool x); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Runtime.InteropServices; @@ -6011,7 +6078,7 @@ interface I bool Goo([MarshalAs(UnmanagedType.U1)] bool x); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Runtime.InteropServices; @@ -6029,7 +6096,7 @@ bool I.Goo(bool x) throw new System.NotImplementedException(); } }", -index: 1); +codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(546443, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546443")] @@ -6044,7 +6111,7 @@ interface IGoo void Bar(DateTime DateTime); } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { }", @"using System; @@ -6077,7 +6144,7 @@ interface I void Goo(); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Collections.Generic; @@ -6112,7 +6179,7 @@ public interface I } } -class C : [|N.I|] +class C : {|CS0535:N.I|} { }", @"namespace N @@ -6145,7 +6212,7 @@ public interface I } } -class C : [|N.I|] +class C : {|CS0535:N.I|} { }", @"using N; @@ -6164,7 +6231,7 @@ void I.M() { throw new System.NotImplementedException(); } -}", index: 1); +}", codeAction: ("True;False;False:global::N.I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(847464, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/847464")] @@ -6181,7 +6248,7 @@ partial class C { } -partial class C : [|I|] +partial class C : {|CS0535:I|} { }", @"public interface I @@ -6199,7 +6266,7 @@ void I.Goo() { throw new System.NotImplementedException(); } -}", index: 1); +}", codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(847464, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/847464")] @@ -6212,7 +6279,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void Goo(); } -partial class C : [|I|] +partial class C : {|CS0535:I|} { } @@ -6234,15 +6301,16 @@ void I.Goo() partial class C { -}", index: 1); +}", codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(847464, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/847464")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementInterfaceForPartialType3() { - await TestWithAllCodeStyleOptionsOffAsync( -@"public interface I + await new VerifyCS.Test + { + TestCode = @"public interface I { void Goo(); } @@ -6252,14 +6320,18 @@ public interface I2 void Goo2(); } -partial class C : [|I|] +partial class C : {|CS0535:I|} { } -partial class C : I2 +partial class C : {|CS0535:I2|} { }", -@"public interface I + FixedState = + { + Sources = + { + @"public interface I { void Goo(); } @@ -6277,35 +6349,57 @@ void I.Goo() } } -partial class C : I2 +partial class C : {|CS0535:I2|} { -}", index: 1); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [WorkItem(752447, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/752447")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestExplicitImplOfIndexedProperty() { - var initial = @" - - - + var test = new VerifyCS.Test + { + TestState = + { + Sources = + { + @" +public class Test : {|CS0535:{|CS0535:IGoo|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1", LanguageNames.VisualBasic] = + { + Sources = + { + @" Public Interface IGoo Property IndexProp(ByVal p1 As Integer) As String -End Interface - - - - Assembly1 - -public class Test : [|IGoo|] -{ -} - - -"; - - var expected = @" +End Interface", + }, + }, + }, + AdditionalProjectReferences = + { + "Assembly1", + }, + }, + FixedState = + { + Sources = + { + @" public class Test : IGoo { string IGoo.get_IndexProp(int p1) @@ -6317,39 +6411,52 @@ void IGoo.set_IndexProp(int p1, string Value) { throw new System.NotImplementedException(); } -} - "; +}", + }, + }, + CodeActionEquivalenceKey = "True;False;False:global::IGoo;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }; - await TestWithAllCodeStyleOptionsOffAsync(initial, expected, index: 1); + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } [WorkItem(602475, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/602475")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplicitImplOfIndexedProperty() { - var initial = @" - - - -Public Interface I + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"using System; + +class C : {|CS0535:{|CS0535:I|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1", LanguageNames.VisualBasic] = + { + Sources = + { + @"Public Interface I Property P(x As Integer) -End Interface - - - - Assembly1 - -using System; - -class C : [|I|] -{ -} - - -"; - - var expected = @" -using System; +End Interface", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"using System; class C : I { @@ -6362,40 +6469,56 @@ public void set_P(int x, object Value) { throw new NotImplementedException(); } -} - "; - - await TestWithAllCodeStyleOptionsOffAsync(initial, expected, index: 0); +}", + }, + }, + CodeActionEquivalenceKey = "False;False;True:global::I;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementationOfIndexerWithInaccessibleAttributes() { - var initial = @" - - - + var test = new VerifyCS.Test + { + TestState = + { + Sources = + { + @" +using System; + +class C : {|CS0535:I|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @" using System; internal class ShouldBeRemovedAttribute : Attribute { } public interface I { string this[[ShouldBeRemovedAttribute] int i] { get; set; } -} - - - - Assembly1 - -using System; - -class C : [|I|] -{ -} - - -"; - - var expected = @" +}" + }, + }, + }, + AdditionalProjectReferences = + { + "Assembly1", + }, + }, + FixedState = + { + Sources = + { + @" using System; class C : I @@ -6412,10 +6535,15 @@ public string this[int i] throw new NotImplementedException(); } } -} - "; +}", + }, + }, + CodeActionEquivalenceKey = "False;False;True:global::I;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }; - await TestWithAllCodeStyleOptionsOffAsync(initial, expected, index: 0); + test.Options.AddRange(AllOptionsOff); + await test.RunAsync(); } #if false @@ -6438,7 +6566,7 @@ public async Task TestImplementInterfaceForImplicitIDisposable() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class Program : [|IDisposable|] +class Program : {|CS0535:IDisposable|} { }", $@"using System; @@ -6448,7 +6576,7 @@ class Program : IDisposable private bool disposedValue; {DisposePattern("protected virtual ", "Program", "public void ")} -}}", index: 1); +}}", codeAction: ("False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -6457,7 +6585,7 @@ public async Task TestImplementInterfaceForExplicitIDisposable() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class Program : [|IDisposable|] +class Program : {|CS0535:IDisposable|} { private bool DisposedValue; }", @@ -6469,7 +6597,7 @@ class Program : IDisposable private bool disposedValue; {DisposePattern("protected virtual ", "Program", "void IDisposable.")} -}}", index: 3); +}}", codeAction: ("True;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 3)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -6478,7 +6606,7 @@ public async Task TestImplementInterfaceForIDisposableNonApplicable1() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class Program : [|IDisposable|] +class Program : {|CS0535:IDisposable|} { private bool disposedValue; }", @@ -6492,7 +6620,7 @@ public void Dispose() { throw new NotImplementedException(); } -}", index: 0); +}", codeAction: ("False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 0)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -6501,7 +6629,7 @@ public async Task TestImplementInterfaceForIDisposableNonApplicable2() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class Program : [|IDisposable|] +class Program : {|CS0535:IDisposable|} { public void Dispose(bool flag) { @@ -6519,7 +6647,7 @@ public void Dispose() { throw new NotImplementedException(); } -}", index: 0); +}", codeAction: ("False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 0)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -6528,7 +6656,7 @@ public async Task TestImplementInterfaceForExplicitIDisposableWithSealedClass() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -sealed class Program : [|IDisposable|] +sealed class Program : {|CS0535:IDisposable|} { }", $@"using System; @@ -6538,7 +6666,7 @@ sealed class Program : IDisposable private bool disposedValue; {DisposePattern("private ", "Program", "void IDisposable.")} -}}", index: 3); +}}", codeAction: ("True;False;False:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 3)); } [WorkItem(9760, "https://github.com/dotnet/roslyn/issues/9760")] @@ -6548,7 +6676,7 @@ public async Task TestImplementInterfaceForExplicitIDisposableWithExistingField( await TestWithAllCodeStyleOptionsOffAsync( @"using System; -class Program : [|IDisposable|] +class Program : {|CS0535:IDisposable|} { private bool disposedValue; }", @@ -6560,27 +6688,35 @@ class Program : IDisposable private bool disposedValue1; {DisposePattern("protected virtual ", "Program", "public void ", disposeField: "disposedValue1")} -}}", index: 1); +}}", codeAction: ("False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 1)); } [WorkItem(9760, "https://github.com/dotnet/roslyn/issues/9760")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementInterfaceUnderscoreNameForFields() { - await TestInRegularAndScriptAsync( -@"using System; + await new VerifyCS.Test + { + TestCode = @"using System; -class Program : [|IDisposable|] +class Program : {|CS0535:IDisposable|} { }", -$@"using System; + FixedCode = $@"using System; class Program : IDisposable {{ private bool _disposedValue; {DisposePattern("protected virtual ", "Program", "public void ", disposeField: "_disposedValue")} -}}", index: 1, options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix); +}}", + Options = + { + _options.FieldNamesAreCamelCaseWithUnderscorePrefix, + }, + CodeActionEquivalenceKey = "False;False;True:global::System.IDisposable;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [WorkItem(939123, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/939123")] @@ -6593,7 +6729,7 @@ await TestWithAllCodeStyleOptionsOffAsync( void M([System.Runtime.InteropServices.ComAliasName(""pAlias"")] int p); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -6623,7 +6759,7 @@ interface I long M([ComAliasName(""pAlias2"")] int p); } -class C : [|I|] +class C : {|CS0535:I|} { }", @"using System.Runtime.InteropServices; @@ -6653,7 +6789,7 @@ await TestWithAllCodeStyleOptionsOffAsync( long this[[System.Runtime.InteropServices.ComAliasName(""pAlias"")] int p] { get; } } -class C : [|I|] +class C : {|CS0535:I|} { }", @"interface I @@ -6685,7 +6821,7 @@ public interface TestInterface void M1(); } - struct TestStruct1 : [|TestInterface|] + struct TestStruct1 : {|CS0535:TestInterface|}{|CS1513:|}{|CS1514:|} // Comment @@ -6726,7 +6862,7 @@ partial class C { } -partial class C : [|I|], System.IDisposable +partial class C : {|CS0535:{|CS0535:{|CS0535:I|}|}|}, {|CS0535:System.IDisposable|} { }", $@"using System; @@ -6762,7 +6898,7 @@ public List M(Dictionary> a, TT b, UU c) where UU : TT }} {DisposePattern("protected virtual ", "C", "public void ")} -}}", index: 1); +}}", codeAction: ("False;False;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 1)); } [WorkItem(994328, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994328")] @@ -6776,7 +6912,7 @@ await TestWithAllCodeStyleOptionsOffAsync( System.Collections.Generic.List M(System.Collections.Generic.Dictionary> a, TT b, UU c) where UU : TT; } -partial class C : [|I|], System.IDisposable +partial class C : {|CS0535:{|CS0535:{|CS0535:I|}|}|}, {|CS0535:System.IDisposable|} { } @@ -6816,7 +6952,7 @@ List I.M(Dictionary> a, partial class C {{ -}}", index: 3); +}}", codeAction: ("True;False;False:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", 3)); } private static string DisposePattern( @@ -6863,7 +6999,7 @@ public async Task TestInaccessibleAttributes() await TestWithAllCodeStyleOptionsOffAsync( @"using System; -public class Goo : [|Holder.SomeInterface|] +public class Goo : {|CS0535:Holder.SomeInterface|} { } @@ -6911,7 +7047,7 @@ await TestWithAllCodeStyleOptionsOffAsync( using System.Linq; using System.Threading.Tasks; -class Issue2785 : [|IList|] +class Issue2785 : {|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:IList|}|}|}|}|}|}|}|}|}|}|}|}|} { private static List innerList = new List(); }", @@ -7004,7 +7140,7 @@ IEnumerator IEnumerable.GetEnumerator() return ((IEnumerable)innerList).GetEnumerator(); } }", -index: 1); +codeAction: ("False;False;False:global::System.Collections.Generic.IList;mscorlib;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;innerList", 1)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface), CompilerTrait(CompilerFeature.Tuples)] @@ -7016,7 +7152,7 @@ await TestWithAllCodeStyleOptionsOffAsync( (int, string, int, string, int, string, int, string) Method1((int, string, int, string, int, string, int, string) y); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { (int, string) x; }", @@ -7045,7 +7181,7 @@ await TestWithAllCodeStyleOptionsOffAsync( (int a, string b, int c, string d, int e, string f, int g, string h) Method1((int a, string b, int c, string d, int e, string f, int g, string h) y); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { (int, string) x; }", @@ -7074,7 +7210,7 @@ await TestWithAllCodeStyleOptionsOffAsync( (TA, TB) Method1((TA, TB) y); } -class Class : [|IInterface<(int, string), int>|] +class Class : {|CS0535:IInterface<(int, string), int>|} { (int, string) x; }", @@ -7103,7 +7239,7 @@ await TestWithAllCodeStyleOptionsOffAsync( (TA a, TB b) Method1((TA a, TB b) y); } -class Class : [|IInterface<(int, string), int>|] +class Class : {|CS0535:IInterface<(int, string), int>|} { (int, string) x; }", @@ -7127,17 +7263,18 @@ class Class : IInterface<(int, string), int> [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestWithGroupingOff1() { - await TestInRegularAndScriptAsync( -@"interface IInterface + await new VerifyCS.Test + { + TestCode = @"interface IInterface { int Prop { get; } } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { void M() { } }", -@"interface IInterface + FixedCode = @"interface IInterface { int Prop { get; } } @@ -7147,7 +7284,12 @@ class Class : IInterface void M() { } public int Prop => throw new System.NotImplementedException(); -}", options: Option(ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd)); +}", + Options = + { + { ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd }, + }, + }.RunAsync(); } [WorkItem(15387, "https://github.com/dotnet/roslyn/issues/15387")] @@ -7159,6 +7301,7 @@ await TestInRegularAndScriptAsync( using System.Runtime.InteropServices; [ComImport] +[Guid(""00000000-0000-0000-0000-000000000000"")] interface IComInterface { void MOverload(); @@ -7167,13 +7310,14 @@ interface IComInterface int Prop { get; } } -class Class : [|IComInterface|] +class Class : {|CS0535:{|CS0535:{|CS0535:{|CS0535:IComInterface|}|}|}|} { }", @" using System.Runtime.InteropServices; [ComImport] +[Guid(""00000000-0000-0000-0000-000000000000"")] interface IComInterface { void MOverload(); @@ -7207,11 +7351,15 @@ public void MOverload(int i) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestDoNotReorderComImportMembers_02() { - await TestInRegularAndScriptAsync( + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @" using System.Runtime.InteropServices; [ComImport] +[Guid(""00000000-0000-0000-0000-000000000000"")] interface IComInterface { void MOverload() { } @@ -7220,13 +7368,15 @@ void MOverload(int i) { } int Prop { get; } } -class Class : [|IComInterface|] +class Class : {|CS0535:IComInterface|} { }", + FixedCode = @" using System.Runtime.InteropServices; [ComImport] +[Guid(""00000000-0000-0000-0000-000000000000"")] interface IComInterface { void MOverload() { } @@ -7238,7 +7388,8 @@ void MOverload(int i) { } class Class : IComInterface { public int Prop => throw new System.NotImplementedException(); -}"); +}", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -7254,7 +7405,7 @@ interface I { ref int this[int i] { get; } } -class C : [|I|] +class C : {|CS0535:{|CS0535:{|CS0535:I|}|}|} { }", @" @@ -7284,18 +7435,19 @@ public ref int IGoo() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestAutoProperties() { - await TestInRegularAndScript1Async( -@"interface IInterface + await new VerifyCS.Test() + { + TestCode = @"interface IInterface { int ReadOnlyProp { get; } int ReadWriteProp { get; set; } int WriteOnlyProp { set; } } -class Class : [|IInterface|] +class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { int ReadOnlyProp { get; } int ReadWriteProp { get; set; } @@ -7307,16 +7459,21 @@ class Class : IInterface public int ReadOnlyProp { get; } public int ReadWriteProp { get; set; } public int WriteOnlyProp { set => throw new System.NotImplementedException(); } -}", parameters: new TestParameters(options: Option( - ImplementTypeOptions.PropertyGenerationBehavior, - ImplementTypePropertyGenerationBehavior.PreferAutoProperties))); +}", + Options = + { + { ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties }, + }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestOptionalParameterWithDefaultLiteral() { - await TestWithAllCodeStyleOptionsOffAsync( -@" + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp7_1, + TestCode = @" using System.Threading; interface IInterface @@ -7324,10 +7481,10 @@ interface IInterface void Method1(CancellationToken cancellationToken = default(CancellationToken)); } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@" + FixedCode = @" using System.Threading; interface IInterface @@ -7341,7 +7498,9 @@ public void Method1(CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } -}", parseOptions: CSharp7_1); +}", + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -7352,7 +7511,7 @@ await TestInRegularAndScriptAsync( { void Method(in int p); } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"interface ITest @@ -7376,7 +7535,7 @@ await TestInRegularAndScriptAsync( { ref readonly int Method(); } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"interface ITest @@ -7400,7 +7559,7 @@ await TestInRegularAndScriptAsync( { ref readonly int Property { get; } } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"interface ITest @@ -7421,7 +7580,7 @@ await TestInRegularAndScriptAsync( { int this[in int p] { set; } } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"interface ITest @@ -7442,7 +7601,7 @@ await TestInRegularAndScriptAsync( { ref readonly int this[int p] { get; } } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"interface ITest @@ -7463,7 +7622,7 @@ await TestInRegularAndScriptAsync( { void M() where T : unmanaged; } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"public interface ITest @@ -7482,8 +7641,10 @@ public void M() where T : unmanaged [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestSealedMember_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7491,10 +7652,10 @@ sealed void M1() {} sealed int P1 => 1; } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -7508,14 +7669,18 @@ public void Method1() { throw new System.NotImplementedException(); } -}"); +}", + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestSealedMember_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7523,10 +7688,10 @@ sealed void M1() {} sealed int P1 => 1; } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -7541,14 +7706,19 @@ void IInterface.Method1() throw new System.NotImplementedException(); } }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestSealedMember_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7556,10 +7726,10 @@ sealed void M1() {} sealed int P1 => 1; } -abstract class Class : [|IInterface|] +abstract class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -7571,14 +7741,19 @@ abstract class Class : IInterface { public abstract void Method1(); }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;True;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNonPublicMember_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7586,10 +7761,14 @@ await TestWithAllCodeStyleOptionsOffAsync( protected int P1 {get;} } -class Class : [|IInterface|] +class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} { }", -@"interface IInterface + FixedState = + { + Sources = + { + @"interface IInterface { void Method1(); @@ -7597,29 +7776,41 @@ class Class : [|IInterface|] protected int P1 {get;} } -class Class : IInterface +class Class : {|CS0535:{|CS0535:IInterface|}|} { public void Method1() { throw new System.NotImplementedException(); } -}"); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;False;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNonPublicMember_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { protected void M1(); protected int P1 {get;} } -class Class : [|IInterface|] +class Class : {|CS0535:{|CS0535:IInterface|}|} { }", -@"interface IInterface + FixedState = + { + Sources = + { + @"interface IInterface { protected void M1(); protected int P1 {get;} @@ -7640,14 +7831,24 @@ void IInterface.M1() throw new System.NotImplementedException(); } }", -index: 1); + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + DiagnosticSelector = diagnostics => diagnostics[1], + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNonPublicMember_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7655,10 +7856,14 @@ await TestWithAllCodeStyleOptionsOffAsync( protected int P1 {get;} } -abstract class Class : [|IInterface|] +abstract class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} { }", -@"interface IInterface + FixedState = + { + Sources = + { + @"interface IInterface { void Method1(); @@ -7666,18 +7871,25 @@ abstract class Class : [|IInterface|] protected int P1 {get;} } -abstract class Class : IInterface +abstract class Class : {|CS0535:{|CS0535:IInterface|}|} { public abstract void Method1(); }", -index: 1); + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;True;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNonPublicAccessor_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7685,10 +7897,14 @@ await TestWithAllCodeStyleOptionsOffAsync( int P2 {protected get; set;} } -class Class : [|IInterface|] +class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} { }", -@"interface IInterface + FixedState = + { + Sources = + { + @"interface IInterface { void Method1(); @@ -7696,29 +7912,41 @@ class Class : [|IInterface|] int P2 {protected get; set;} } -class Class : IInterface +class Class : {|CS0535:{|CS0535:IInterface|}|} { public void Method1() { throw new System.NotImplementedException(); } -}"); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;False;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNonPublicAccessor_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { int P1 {get; protected set;} int P2 {protected get; set;} } -class Class : [|IInterface|] +class Class : {|CS0535:{|CS0535:IInterface|}|} { }", -@"interface IInterface + FixedState = + { + Sources = + { + @"interface IInterface { int P1 {get; protected set;} int P2 {protected get; set;} @@ -7751,14 +7979,23 @@ int IInterface.P2 throw new System.NotImplementedException(); } } -}"); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestNonPublicAccessor_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7766,10 +8003,14 @@ await TestWithAllCodeStyleOptionsOffAsync( int P2 {protected get; set;} } -abstract class Class : [|IInterface|] +abstract class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} { }", -@"interface IInterface + FixedState = + { + Sources = + { + @"interface IInterface { void Method1(); @@ -7777,18 +8018,25 @@ abstract class Class : [|IInterface|] int P2 {protected get; set;} } -abstract class Class : IInterface +abstract class Class : {|CS0535:{|CS0535:IInterface|}|} { public abstract void Method1(); }", -index: 1); + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;True;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestPrivateAccessor_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7796,10 +8044,10 @@ await TestWithAllCodeStyleOptionsOffAsync( int P2 {private get => 0; set {}} } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -7813,14 +8061,18 @@ public void Method1() { throw new System.NotImplementedException(); } -}"); +}", + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestPrivateAccessor_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7828,10 +8080,10 @@ await TestWithAllCodeStyleOptionsOffAsync( int P2 {private get => 0; set {}} } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -7846,14 +8098,19 @@ void IInterface.Method1() throw new System.NotImplementedException(); } }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestPrivateAccessor_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -7861,10 +8118,10 @@ await TestWithAllCodeStyleOptionsOffAsync( int P2 {private get => 0; set {}} } -abstract class Class : [|IInterface|] +abstract class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -7876,234 +8133,323 @@ abstract class Class : IInterface { public abstract void Method1(); }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;True;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestInaccessibleMember_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@" - - - -public interface IInterface + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface IInterface { void Method1(); internal void M1(); internal int P1 {get;} -} - - - - Assembly1 - -class Class : [|IInterface|] -{ -} - - -", -@" -class Class : IInterface +}", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:IInterface|}|} { public void Method1() { throw new System.NotImplementedException(); } -} - "); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + + // Specify the code action by equivalence key only to avoid trying to implement the interface explicitly with a second code fix pass. + CodeActionEquivalenceKey = "False;False;True:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestInaccessibleMember_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@" - - - -public interface IInterface + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface IInterface { void Method1(); internal void M1(); internal int P1 {get;} -} - - - - Assembly1 - -class Class : [|IInterface|] -{ -} - - -", -@" -class Class : IInterface +}", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:IInterface|}|} { void IInterface.Method1() { throw new System.NotImplementedException(); } -} - ", -index: 1); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestInaccessibleMember_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@" - - - -public interface IInterface + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"abstract class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface IInterface { void Method1(); internal void M1(); internal int P1 {get;} -} - - - - Assembly1 - -abstract class Class : [|IInterface|] -{ -} - - -", -@" -abstract class Class : IInterface +}", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"abstract class Class : {|CS0535:{|CS0535:IInterface|}|} { public abstract void Method1(); -} - ", -index: 1); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + + // Specify the code action by equivalence key only to avoid trying to execute a second code fix pass with a different action + CodeActionEquivalenceKey = "False;True;True:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestInaccessibleAccessor_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@" - - - -public interface IInterface + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface IInterface { void Method1(); int P1 {get; internal set;} int P2 {internal get; set;} -} - - - - Assembly1 - -class Class : [|IInterface|] -{ -} - - -", -@" -class Class : IInterface +}", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:IInterface|}|} { public void Method1() { throw new System.NotImplementedException(); } -} - "); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + + // Specify the code action by equivalence key only to avoid trying to implement the interface explicitly with a second code fix pass. + CodeActionEquivalenceKey = "False;False;True:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestInaccessibleAccessor_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@" - - - -public interface IInterface + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface IInterface { void Method1(); int P1 {get; internal set;} int P2 {internal get; set;} -} - - - - Assembly1 - -class Class : [|IInterface|] -{ -} - - -", -@" -class Class : IInterface +}", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"class Class : {|CS0535:{|CS0535:IInterface|}|} { void IInterface.Method1() { throw new System.NotImplementedException(); } -} - ", -index: 1); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestInaccessibleAccessor_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@" - - - -public interface IInterface + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"abstract class Class : {|CS0535:{|CS0535:{|CS0535:IInterface|}|}|} +{ +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface IInterface { void Method1(); int P1 {get; internal set;} int P2 {internal get; set;} -} - - - - Assembly1 - -abstract class Class : [|IInterface|] -{ -} - - -", -@" -abstract class Class : IInterface +}", + }, + }, + }, + AdditionalProjectReferences = { "Assembly1" }, + }, + FixedState = + { + Sources = + { + @"abstract class Class : {|CS0535:{|CS0535:IInterface|}|} { public abstract void Method1(); -} - ", -index: 1); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + Options = { AllOptionsOff }, + + // Specify the code action by equivalence key only to avoid trying to execute a second code fix pass with a different action + CodeActionEquivalenceKey = "False;True;True:global::IInterface;Assembly1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestVirtualMember_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -8111,10 +8457,10 @@ virtual void M1() {} virtual int P1 => 1; } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -8128,14 +8474,18 @@ public void Method1() { throw new System.NotImplementedException(); } -}"); +}", + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestVirtualMember_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -8143,10 +8493,10 @@ virtual void M1() {} virtual int P1 => 1; } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -8161,14 +8511,19 @@ void IInterface.Method1() throw new System.NotImplementedException(); } }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestVirtualMember_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -8176,10 +8531,10 @@ virtual void M1() {} virtual int P1 => 1; } -abstract class Class : [|IInterface|] +abstract class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -8191,14 +8546,19 @@ abstract class Class : IInterface { public abstract void Method1(); }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;True;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticMember_01() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -8208,10 +8568,10 @@ static void M1() {} public abstract class C {} } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -8227,14 +8587,18 @@ public void Method1() { throw new System.NotImplementedException(); } -}"); +}", + Options = { AllOptionsOff }, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticMember_02() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -8244,10 +8608,10 @@ static void M1() {} public abstract class C {} } -class Class : [|IInterface|] +class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -8264,14 +8628,19 @@ void IInterface.Method1() throw new System.NotImplementedException(); } }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "True;False;False:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticMember_03() { - await TestWithAllCodeStyleOptionsOffAsync( -@"interface IInterface + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"interface IInterface { void Method1(); @@ -8281,10 +8650,10 @@ static void M1() {} public abstract class C {} } -abstract class Class : [|IInterface|] +abstract class Class : {|CS0535:IInterface|} { }", -@"interface IInterface + FixedCode = @"interface IInterface { void Method1(); @@ -8298,7 +8667,10 @@ abstract class Class : IInterface { public abstract void Method1(); }", -index: 1); + Options = { AllOptionsOff }, + CodeActionEquivalenceKey = "False;True;True:global::IInterface;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -8309,7 +8681,7 @@ await TestInRegularAndScriptAsync( { void M() where T : notnull; } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"public interface ITest @@ -8335,7 +8707,7 @@ public interface ITest { string? P { get; } } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"#nullable enable @@ -8353,17 +8725,18 @@ public class Test : ITest [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestWithNullablePropertyAlreadyImplemented() { - await TestMissingAsync( + var code = @"#nullable enable public interface ITest { string? P { get; } } -public class Test : [|ITest|] +public class Test : ITest { public string? P => throw new System.NotImplementedException(); -}"); +}"; + await VerifyCS.VerifyCodeFixAsync(code, code); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -8376,7 +8749,7 @@ public interface ITest { string? P(); } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"#nullable enable @@ -8408,7 +8781,7 @@ public interface ITest { event EventHandler? SomeEvent; } -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"#nullable enable @@ -8438,7 +8811,7 @@ public interface ITest #nullable disable -public class Test : [|ITest|] +public class Test : {|CS0535:ITest|} { }", @"#nullable enable @@ -8459,54 +8832,53 @@ public class Test : ITest [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task GenericInterfaceNotNull1() { - await TestInRegularAndScriptAsync( -@$"#nullable enable + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = @"#nullable enable using System.Diagnostics.CodeAnalysis; -{NullableAttributesCode} - interface IFoo -{{ +{ [return: NotNull] T Bar([DisallowNull] T bar); [return: MaybeNull] T Baz([AllowNull] T bar); -}} +} -class A : [|IFoo|] -{{ -}}", -@$"#nullable enable +class A : {|CS0535:{|CS0535:IFoo|}|} +{ +}", + FixedCode = @"#nullable enable using System.Diagnostics.CodeAnalysis; -{NullableAttributesCode} - interface IFoo -{{ +{ [return: NotNull] T Bar([DisallowNull] T bar); [return: MaybeNull] T Baz([AllowNull] T bar); -}} +} -class A : [|IFoo|] -{{ +class A : IFoo +{ [return: NotNull] public int Bar([DisallowNull] int bar) - {{ + { throw new System.NotImplementedException(); - }} + } [return: MaybeNull] public int Baz([AllowNull] int bar) - {{ + { throw new System.NotImplementedException(); - }} -}}"); + } +}", + }.RunAsync(); } [WorkItem(13427, "https://github.com/dotnet/roslyn/issues/13427")] @@ -8524,7 +8896,7 @@ interface I void M(); } -class D : B, [|I|] +class D : B, {|CS0535:I|} { }", @"class B @@ -8556,7 +8928,7 @@ interface I void M2(); } -class C : [|I|] +class C : {|CS0535:I|} { public void M1(){} }", @@ -8567,7 +8939,7 @@ interface I void M2(); } -class C : [|I|] +class C : {|CS0535:I|} { public void M1(){} @@ -8575,64 +8947,94 @@ void I.M2() { throw new System.NotImplementedException(); } -}", index: 2); +}", codeAction: ("True;False;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 2)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task ImplementInitOnlyProperty() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.CSharp9, + TestCode = @" interface I { int Property { get; init; } } -class C : [|I|] +class C : {|CS0535:I|} { }", -@" + FixedCode = @" interface I { int Property { get; init; } } -class C : [|I|] +class C : I { public int Property { get => throw new System.NotImplementedException(); init => throw new System.NotImplementedException(); } -}"); +}", + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task ImplementRemainingExplicitlyMissingWhenAllImplemented() { - await TestActionCountAsync(@" + var code = @" interface I { void M1(); void M2(); } -class C : [|I|] +class C : I { public void M1(){} public void M2(){} -}", 0); +}"; + + await VerifyCS.VerifyCodeFixAsync(code, code); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task ImplementRemainingExplicitlyMissingWhenAllImplementedAreExplicit() { - await TestActionCountAsync(@" + var code = @" interface I { void M1(); void M2(); } -class C : [|I|] +class C : {|CS0535:I|} { void I.M1(){} -}", 2); +}"; + var fixedCode = @" +interface I +{ + void M1(); + void M2(); +} + +class C : I +{ + public void M2() + { + throw new System.NotImplementedException(); + } + + void I.M1(){} +}"; + + await new VerifyCS.Test + { + TestCode = code, + FixedCode = fixedCode, + CodeActionsVerifier = codeActions => Assert.Equal(2, codeActions.Length), + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] @@ -8645,7 +9047,7 @@ interface I internal void M2(); } -class C : [|I|] +class C : {|CS0535:I|} { public void M1(){} }", @@ -8656,7 +9058,7 @@ interface I internal void M2(); } -class C : [|I|] +class C : {|CS0535:I|} { public void M1(){} @@ -8664,63 +9066,73 @@ void I.M2() { throw new System.NotImplementedException(); } -}", index: 1); +}", codeAction: ("True;False;True:global::I;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); } [WorkItem(48295, "https://github.com/dotnet/roslyn/issues/48295")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementOnRecord_WithSemiColon() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" interface I { void M1(); } -record C : [|I|]; +record C : {|CS0535:I|}; ", -@" + FixedCode = @" interface I { void M1(); } -record C : [|I|] +record C : {|CS0535:I|} { public void M1() { throw new System.NotImplementedException(); } } -"); +", + }.RunAsync(); } [WorkItem(48295, "https://github.com/dotnet/roslyn/issues/48295")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementOnRecord_WithBracesAndTrivia() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" interface I { void M1(); } -record C : [|I|] { } // hello +record C : {|CS0535:I|} { } // hello ", -@" + FixedCode = @" interface I { void M1(); } -record C : [|I|] +record C : {|CS0535:I|} { public void M1() { throw new System.NotImplementedException(); } } // hello -"); +", + }.RunAsync(); } [WorkItem(48295, "https://github.com/dotnet/roslyn/issues/48295")] @@ -8730,45 +9142,53 @@ public void M1() [InlineData("record struct")] public async Task TestImplementOnRecord_WithSemiColonAndTrivia(string record) { - await TestInRegularAndScriptAsync($@" + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = $@" interface I {{ void M1(); }} -{record} C : [|I|]; // hello +{record} C : {{|CS0535:I|}}; // hello ", -$@" + FixedCode = $@" interface I {{ void M1(); }} -{record} C : [|I|] // hello +{record} C : {{|CS0535:I|}} // hello {{ public void M1() {{ throw new System.NotImplementedException(); }} }} -"); +", + }.RunAsync(); } [WorkItem(49019, "https://github.com/dotnet/roslyn/issues/49019")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestUnconstrainedGenericInstantiatedWithValueType() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + TestCode = @"#nullable enable interface IGoo { void Bar(T? x); } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { } ", -@" + FixedCode = @"#nullable enable interface IGoo { void Bar(T? x); @@ -8781,7 +9201,8 @@ public void Bar(int x) throw new System.NotImplementedException(); } } -"); +", + }.RunAsync(); } [WorkItem(49019, "https://github.com/dotnet/roslyn/issues/49019")] @@ -8794,7 +9215,7 @@ interface IGoo where T : struct void Bar(T? x); } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { } ", @@ -8818,20 +9239,27 @@ public void Bar(int? x) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestUnconstrainedGenericInstantiatedWithReferenceType() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + TestCode = @" interface IGoo { +#nullable enable void Bar(T? x); +#nullable restore } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { } ", -@" + FixedCode = @" interface IGoo { +#nullable enable void Bar(T? x); +#nullable restore } class C : IGoo @@ -8841,14 +9269,18 @@ public void Bar(string x) throw new System.NotImplementedException(); } } -"); +", + }.RunAsync(); } [WorkItem(49019, "https://github.com/dotnet/roslyn/issues/49019")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestUnconstrainedGenericInstantiatedWithReferenceType_NullableEnable() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + TestCode = @" #nullable enable interface IGoo @@ -8856,11 +9288,11 @@ interface IGoo void Bar(T? x); } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { } ", -@" + FixedCode = @" #nullable enable interface IGoo @@ -8875,24 +9307,30 @@ public void Bar(string? x) throw new System.NotImplementedException(); } } -"); +", + }.RunAsync(); } [WorkItem(49019, "https://github.com/dotnet/roslyn/issues/49019")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestConstrainedGenericInstantiatedWithReferenceType() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + TestCode = @" +#nullable enable interface IGoo where T : class { void Bar(T? x); } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { } ", -@" + FixedCode = @" +#nullable enable interface IGoo where T : class { void Bar(T? x); @@ -8900,12 +9338,13 @@ interface IGoo where T : class class C : IGoo { - public void Bar(string x) + public void Bar(string? x) { throw new System.NotImplementedException(); } } -"); +", + }.RunAsync(); } [WorkItem(49019, "https://github.com/dotnet/roslyn/issues/49019")] @@ -8920,7 +9359,7 @@ interface IGoo where T : class void Bar(T? x); } -class C : [|IGoo|] +class C : {|CS0535:IGoo|} { } ", @@ -8946,18 +9385,21 @@ public void Bar(string? x) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestImplementTwoPropertiesOfCSharp5() { - await TestInRegularAndScriptAsync(@" + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp5, + TestCode = @" interface ITest { int Bar { get; } int Foo { get; } } -class Program : [|ITest|] +class Program : {|CS0535:{|CS0535:ITest|}|} { } ", -@" + FixedCode = @" interface ITest { int Bar { get; } @@ -8982,7 +9424,459 @@ public int Foo } } } -", parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5)); +", + }.RunAsync(); + } + + [WorkItem(53925, "https://github.com/dotnet/roslyn/issues/53925")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceMember() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest +{ + static abstract void M1(); +} + +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest +{ + static abstract void M1(); +} + +class C : ITest +{ + public static void M1() + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface, codeAction.Title), + CodeActionEquivalenceKey = "False;False;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); + } + + [WorkItem(53925, "https://github.com/dotnet/roslyn/issues/53925")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceMemberExplicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest +{ + static abstract void M1(); +} + +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest +{ + static abstract void M1(); +} + +class C : ITest +{ + static void ITest.M1() + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_all_members_explicitly, codeAction.Title), + CodeActionEquivalenceKey = "True;False;False:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); + } + + [WorkItem(53925, "https://github.com/dotnet/roslyn/issues/53925")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceMember_ImplementAbstractly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest +{ + static abstract void M1(); +} + +abstract class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest +{ + static abstract void M1(); +} + +abstract class C : ITest +{ + public static void M1() + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface_abstractly, codeAction.Title), + CodeActionEquivalenceKey = "False;True;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceOperator_OnlyExplicitlyImplementable() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest +{ + static abstract int operator -(ITest x); +} +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest +{ + static abstract int operator -(ITest x); +} +class C : ITest +{ + static int ITest.operator -(ITest x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_all_members_explicitly, codeAction.Title), + CodeActionEquivalenceKey = "True;False;False:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceOperator_ImplementImplicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest where T : ITest +{ + static abstract int operator -(T x); + static abstract int operator -(T x, int y); +} +class C : {|CS0535:{|CS0535:ITest|}|} +{ +} +", + FixedCode = @" +interface ITest where T : ITest +{ + static abstract int operator -(T x); + static abstract int operator -(T x, int y); +} +class C : ITest +{ + public static int operator -(C x) + { + throw new System.NotImplementedException(); + } + + public static int operator -(C x, int y) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface, codeAction.Title), + CodeActionEquivalenceKey = "False;False;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceOperator_ImplementExplicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest where T : ITest +{ + static abstract int operator -(T x); +} +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest where T : ITest +{ + static abstract int operator -(T x); +} +class C : ITest +{ + static int ITest.operator -(C x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_all_members_explicitly, codeAction.Title), + CodeActionEquivalenceKey = "True;False;False:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterfaceOperator_ImplementAbstractly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest where T : ITest +{ + static abstract int operator -(T x); +} +abstract class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest where T : ITest +{ + static abstract int operator -(T x); +} +abstract class C : ITest +{ + public static int operator -(C x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface_abstractly, codeAction.Title), + CodeActionEquivalenceKey = "False;True;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterface_Explicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest +{ + static abstract int M(ITest x); +} +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest +{ + static abstract int M(ITest x); +} +class C : ITest +{ + static int ITest.M(ITest x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_all_members_explicitly, codeAction.Title), + CodeActionEquivalenceKey = "True;False;False:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterface_Implicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest +{ + static abstract int M(ITest x); +} +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest +{ + static abstract int M(ITest x); +} +class C : ITest +{ + public static int M(ITest x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface, codeAction.Title), + CodeActionEquivalenceKey = "False;False;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterface_ImplementImplicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest where T : ITest +{ + static abstract int M(T x); +} +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest where T : ITest +{ + static abstract int M(T x); +} +class C : ITest +{ + public static int M(C x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface, codeAction.Title), + CodeActionEquivalenceKey = "False;False;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterface_ImplementExplicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest where T : ITest +{ + static abstract int M(T x); +} +class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest where T : ITest +{ + static abstract int M(T x); +} +class C : ITest +{ + static int ITest.M(C x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_all_members_explicitly, codeAction.Title), + CodeActionEquivalenceKey = "True;False;False:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + + }.RunAsync(); + } + + [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestStaticAbstractInterface_ImplementAbstractly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface ITest where T : ITest +{ + static abstract int M(T x); +} +abstract class C : {|CS0535:ITest|} +{ +} +", + FixedCode = @" +interface ITest where T : ITest +{ + static abstract int M(T x); +} +abstract class C : ITest +{ + public static int M(C x) + { + throw new System.NotImplementedException(); + } +} +", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface_abstractly, codeAction.Title), + CodeActionEquivalenceKey = "False;True;True:global::ITest;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + + }.RunAsync(); } } } diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs index 7bdfffaf01dbd..71951cbf5097b 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs @@ -2,16 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading.Tasks; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Xunit; -using ImplementInterfaceCodeAction = Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService.ImplementInterfaceCodeAction; +using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeFixVerifier< + Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, + Microsoft.CodeAnalysis.CSharp.ImplementInterface.CSharpImplementInterfaceCodeFixProvider>; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ImplementInterface { - public partial class ImplementInterfaceTests + public class ImplementInterfaceTests_FixAllTests { #region "Fix all occurrences tests" @@ -20,11 +21,13 @@ public partial class ImplementInterfaceTests [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)] public async Task TestFixAllInDocument() { - var input = @" - - - -public interface I1 + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -34,40 +37,75 @@ public interface I2 void F1(); } -class B1 : {|FixAllInDocument:I1|}, I2 +class B1 : {|CS0535:I1|}, {|CS0535:I2|} { - class C1 : I1, I2 + class C1 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - -class B2 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} { - class C2 : I1, I2 + class C2 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - - - Assembly1 - -class B3 : I1, I2 +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"class B3 : {|CS0535:I1|}, {|CS0535:I2|} { - class C3 : I1, I2 + class C3 : {|CS0535:I1|}, {|CS0535:I2|} { } +}", + }, + AdditionalProjectReferences = { "TestProject" }, + }, + }, + }, + FixedState = + { + Sources = + { + @"public interface I1 +{ + void F1(); } - - -"; - var expected = @" - - - -public interface I1 +public interface I2 +{ + void F1(); +} + +class B1 : I1, I2 +{ + public void F1() + { + throw new System.NotImplementedException(); + } + + class C1 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} +{ + class C2 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + BatchFixedState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -91,31 +129,20 @@ public void F1() throw new System.NotImplementedException(); } } -} - - -class B2 : I1, I2 -{ - class C2 : I1, I2 - { - } -} - - - - Assembly1 - -class B3 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} { - class C3 : I1, I2 + class C2 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - -"; - - await TestInRegularAndScriptAsync(input, expected); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne | CodeFixTestBehaviors.SkipFixAllInProjectCheck | CodeFixTestBehaviors.SkipFixAllInSolutionCheck, + CodeActionEquivalenceKey = "False;False;True:global::I1;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); } [Fact] @@ -123,11 +150,13 @@ class C3 : I1, I2 [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)] public async Task TestFixAllInProject() { - var input = @" - - - -public interface I1 + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -137,40 +166,75 @@ public interface I2 void F1(); } -class B1 : {|FixAllInProject:I1|}, I2 +class B1 : {|CS0535:I1|}, {|CS0535:I2|} { - class C1 : I1, I2 + class C1 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - -class B2 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} { - class C2 : I1, I2 + class C2 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - - - Assembly1 - -class B3 : I1, I2 +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"class B3 : {|CS0535:I1|}, {|CS0535:I2|} { - class C3 : I1, I2 + class C3 : {|CS0535:I1|}, {|CS0535:I2|} { } +}", + }, + AdditionalProjectReferences = { "TestProject" }, + }, + }, + }, + FixedState = + { + Sources = + { + @"public interface I1 +{ + void F1(); } - - -"; - var expected = @" - - - -public interface I1 +public interface I2 +{ + void F1(); +} + +class B1 : I1, I2 +{ + public void F1() + { + throw new System.NotImplementedException(); + } + + class C1 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} +{ + class C2 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + BatchFixedState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -194,10 +258,8 @@ public void F1() throw new System.NotImplementedException(); } } -} - - -class B2 : I1, I2 +}", + @"class B2 : I1, I2 { public void F1() { @@ -211,23 +273,14 @@ public void F1() throw new System.NotImplementedException(); } } -} - - - - Assembly1 - -class B3 : I1, I2 -{ - class C3 : I1, I2 - { - } -} - - -"; - - await TestInRegularAndScriptAsync(input, expected); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne | CodeFixTestBehaviors.SkipFixAllInDocumentCheck | CodeFixTestBehaviors.SkipFixAllInSolutionCheck, + CodeActionEquivalenceKey = "False;False;True:global::I1;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); } [Fact] @@ -235,11 +288,13 @@ class C3 : I1, I2 [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)] public async Task TestFixAllInSolution() { - var input = @" - - - -public interface I1 + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -249,40 +304,75 @@ public interface I2 void F1(); } -class B1 : I1, {|FixAllInSolution:I2|} +class B1 : {|CS0535:I1|}, {|CS0535:I2|} { - class C1 : I1, I2 + class C1 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - -class B2 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} { - class C2 : I1, I2 + class C2 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - - - Assembly1 - -class B3 : I1, I2 +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"class B3 : {|CS0535:I1|}, {|CS0535:I2|} { - class C3 : I1, I2 + class C3 : {|CS0535:I1|}, {|CS0535:I2|} { } +}", + }, + AdditionalProjectReferences = { "TestProject" }, + }, + }, + }, + FixedState = + { + Sources = + { + @"public interface I1 +{ + void F1(); } - - -"; - var expected = @" - - - -public interface I1 +public interface I2 +{ + void F1(); +} + +class B1 : {|CS0535:I1|}, I2 +{ + void I2.F1() + { + throw new System.NotImplementedException(); + } + + class C1 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} +{ + class C2 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + BatchFixedState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -292,63 +382,69 @@ public interface I2 void F1(); } -class B1 : I1, I2 +class B1 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } - class C1 : I1, I2 + class C1 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } } -} - - -class B2 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } - class C2 : I1, I2 + class C2 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } } -} - - - - Assembly1 - -class B3 : I1, I2 +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"class B3 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } - class C3 : I1, I2 + class C3 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } } -} - - -"; - - await TestInRegularAndScriptAsync(input, expected, index: 1); +}", + }, + AdditionalProjectReferences = { "TestProject" }, + }, + }, + MarkupHandling = MarkupMode.Allow, + }, + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne | CodeFixTestBehaviors.SkipFixAllInDocumentCheck | CodeFixTestBehaviors.SkipFixAllInProjectCheck, + DiagnosticSelector = diagnostics => diagnostics[1], + CodeActionEquivalenceKey = "True;False;False:global::I2;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } [Fact] @@ -356,11 +452,13 @@ void I2.F1() [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)] public async Task TestFixAllInSolution_DifferentAssemblyWithSameTypeName() { - var input = @" - - - -public interface I1 + await new VerifyCS.Test + { + TestState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -370,25 +468,26 @@ public interface I2 void F1(); } -class B1 : I1, {|FixAllInSolution:I2|} +class B1 : {|CS0535:I1|}, {|CS0535:I2|} { - class C1 : I1, I2 + class C1 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - -class B2 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} { - class C2 : I1, I2 + class C2 : {|CS0535:I1|}, {|CS0535:I2|} { } -} - - - - -public interface I1 +}", + }, + AdditionalProjects = + { + ["Assembly1"] = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -398,21 +497,55 @@ public interface I2 void F1(); } -class B3 : I1, I2 +class B3 : {|CS0535:I1|}, {|CS0535:I2|} { - class C3 : I1, I2 + class C3 : {|CS0535:I1|}, {|CS0535:I2|} { } +}", + }, + }, + }, + }, + FixedState = + { + Sources = + { + @"public interface I1 +{ + void F1(); +} + +public interface I2 +{ + void F1(); } - - -"; - var expected = @" - - - -public interface I1 +class B1 : {|CS0535:I1|}, I2 +{ + void I2.F1() + { + throw new System.NotImplementedException(); + } + + class C1 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + @"class B2 : {|CS0535:I1|}, {|CS0535:I2|} +{ + class C2 : {|CS0535:I1|}, {|CS0535:I2|} + { + } +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + BatchFixedState = + { + Sources = + { + @"public interface I1 { void F1(); } @@ -422,63 +555,44 @@ public interface I2 void F1(); } -class B1 : I1, I2 +class B1 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } - class C1 : I1, I2 + class C1 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } } -} - - -class B2 : I1, I2 +}", + @"class B2 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } - class C2 : I1, I2 + class C2 : {|CS0535:I1|}, I2 { void I2.F1() { throw new System.NotImplementedException(); } } -} - - - - -public interface I1 -{ - void F1(); -} - -public interface I2 -{ - void F1(); -} - -class B3 : I1, I2 -{ - class C3 : I1, I2 - { - } -} - - -"; - - await TestInRegularAndScriptAsync(input, expected, index: 1); +}", + }, + MarkupHandling = MarkupMode.Allow, + }, + CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne | CodeFixTestBehaviors.SkipFixAllInDocumentCheck | CodeFixTestBehaviors.SkipFixAllInProjectCheck, + DiagnosticSelector = diagnostics => diagnostics[1], + CodeActionEquivalenceKey = "True;False;False:global::I2;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); } #endregion diff --git a/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs b/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs index e14a8ea6bf880..e7d6f5af75de2 100644 --- a/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs +++ b/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs @@ -76,6 +76,7 @@ internal static async Task VerifyExpectedTextAsync(string intentName, string act { edit.Replace(change.Span.ToSpan(), change.NewText); } + edit.Apply(); Assert.Equal(expectedText, textBuffer.CurrentSnapshot.GetText()); diff --git a/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs b/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs index b54438aba8621..fcbab10496bcc 100644 --- a/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs +++ b/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs @@ -1037,5 +1037,100 @@ await TestInRegularAndScriptAsync( @"class C { void M(object o) { [||]if (o is C) { a(); } else { } } }", @"class C { void M(object o) { if (o is not C) { } else { a(); } } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] + [WorkItem(43224, "https://github.com/dotnet/roslyn/issues/43224")] + public async Task TestEmptyIf() + { + await TestInRegularAndScriptAsync( + @"class C { void M(string s){ [||]if (s == ""a""){}else{ s = ""b""}}}", + @"class C { void M(string s){ if (s != ""a""){ s = ""b""}}}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] + [WorkItem(43224, "https://github.com/dotnet/roslyn/issues/43224")] + public async Task TestOnlySingleLineCommentIf() + { + await TestInRegularAndScriptAsync( + @" +class C +{ + void M(string s) + { + [||]if (s == ""a"") + { + // A single line comment + } + else + { + s = ""b"" + } + } +}", + @" +class C +{ + void M(string s) + { + if (s != ""a"") + { + s = ""b"" + } + else + { + // A single line comment + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] + [WorkItem(43224, "https://github.com/dotnet/roslyn/issues/43224")] + public async Task TestOnlyMultilineLineCommentIf() + { + await TestInRegularAndScriptAsync( + @" +class C +{ + void M(string s) + { + [||]if (s == ""a"") + { + /* + * This is + * a multiline + * comment with + * two words + * per line. + */ + } + else + { + s = ""b"" + } + } +}", + @" +class C +{ + void M(string s) + { + if (s != ""a"") + { + s = ""b"" + } + else + { + /* + * This is + * a multiline + * comment with + * two words + * per line. + */ + } + } +}"); + } } } diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index 4973fdf7d92fe..3e663dcaa5826 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -6861,6 +6861,76 @@ await TestInClassAsync(markup, Documentation("Summary documentation")); } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestInheritdocTwoLevels1() + { + var markup = +@" +/// Summary documentation +/// Remarks documentation +void M() { } + +/// +void M(int x) { } + +/// +void $$M(int x, int y) { }"; + + await TestInClassAsync(markup, + MainDescription("void C.M(int x, int y)"), + Documentation("Summary documentation")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestInheritdocTwoLevels2() + { + var markup = +@" +/// Summary documentation +/// Remarks documentation +void M() { } + +/// +void M(int x) { } + +/// +void $$M(int x, int y) { }"; + + await TestInClassAsync(markup, + MainDescription("void C.M(int x, int y)"), + Documentation("Summary documentation")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestInheritdocWithTypeParamRef() + { + var markup = +@" +public class Program +{ + public static void Main() => _ = new Test().$$Clone(); +} + +public class Test : ICloneable> +{ + /// + public Test Clone() => new(); +} + +/// A type that has clonable instances. +/// The type of instances that can be cloned. +public interface ICloneable +{ + /// Clones a . + /// A clone of the . + public T Clone(); +}"; + + await TestInClassAsync(markup, + MainDescription("Test Test.Clone()"), + Documentation("Clones a Test.")); + } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] public async Task TestInheritdocCycle1() { @@ -7304,5 +7374,228 @@ void M2() }", MainDescription($"({FeaturesResources.local_variable}) string? x")); } + + [WorkItem(53135, "https://github.com/dotnet/roslyn/issues/53135")] + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestDocumentationCData() + { + var markup = +@"using I$$ = IGoo; +/// +/// summary for interface IGoo +/// y = null; +/// ]]> +/// +interface IGoo { }"; + + await TestAsync(markup, + MainDescription("interface IGoo"), + Documentation(@"summary for interface IGoo + +List y = null;")); + } + + [WorkItem(37503, "https://github.com/dotnet/roslyn/issues/37503")] + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task DoNotNormalizeWhitespaceForCode() + { + var markup = +@"using I$$ = IGoo; +/// +/// Normalize this, and Also this +/// +/// line 1 +/// line 2 +/// +/// +interface IGoo { }"; + + await TestAsync(markup, + MainDescription("interface IGoo"), + Documentation(@"Normalize this, and Also this + +line 1 +line 2")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestStaticAbstract_ImplicitImplementation() + { + var code = @" +interface I1 +{ + /// Summary text + static abstract void M1(); +} + +class C1_1 : I1 +{ + public static void $$M1() { } +} +"; + + await TestAsync( + code, + MainDescription("void C1_1.M1()"), + Documentation("Summary text")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestStaticAbstract_ImplicitImplementation_FromReference() + { + var code = @" +interface I1 +{ + /// Summary text + static abstract void M1(); +} + +class C1_1 : I1 +{ + public static void M1() { } +} + +class R +{ + public static void M() { C1_1.$$M1(); } +} +"; + + await TestAsync( + code, + MainDescription("void C1_1.M1()"), + Documentation("Summary text")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestStaticAbstract_FromTypeParameterReference() + { + var code = @" +interface I1 +{ + /// Summary text + static abstract void M1(); +} + +class R +{ + public static void M() where T : I1 { T.$$M1(); } +} +"; + + await TestAsync( + code, + MainDescription("void I1.M1()"), + Documentation("Summary text")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestStaticAbstract_ExplicitInheritdoc_ImplicitImplementation() + { + var code = @" +interface I1 +{ + /// Summary text + static abstract void M1(); +} + +class C1_1 : I1 +{ + /// + public static void $$M1() { } +} +"; + + await TestAsync( + code, + MainDescription("void C1_1.M1()"), + Documentation("Summary text")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestStaticAbstract_ExplicitImplementation() + { + var code = @" +interface I1 +{ + /// Summary text + static abstract void M1(); +} + +class C1_1 : I1 +{ + static void I1.$$M1() { } +} +"; + + await TestAsync( + code, + MainDescription("void C1_1.M1()"), + Documentation("Summary text")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestStaticAbstract_ExplicitInheritdoc_ExplicitImplementation() + { + var code = @" +interface I1 +{ + /// Summary text + static abstract void M1(); +} + +class C1_1 : I1 +{ + /// + static void I1.$$M1() { } +} +"; + + await TestAsync( + code, + MainDescription("void C1_1.M1()"), + Documentation("Summary text")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task QuickInfoLambdaReturnType_01() + { + await TestWithOptionsAsync( + Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9), +@"class Program +{ + System.Delegate D = bo$$ol () => true; +}", + MainDescription("struct System.Boolean")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task QuickInfoLambdaReturnType_02() + { + await TestWithOptionsAsync( + Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9), +@"class A +{ + struct B { } + System.Delegate D = A.B$$ () => null; +}", + MainDescription("struct A.B")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task QuickInfoLambdaReturnType_03() + { + await TestWithOptionsAsync( + Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9), +@"class A +{ +} +struct B +{ + System.Delegate D = A () => null; +}", + MainDescription("struct B")); + } } } diff --git a/src/EditorFeatures/CSharpTest/SignatureHelp/InvocationExpressionSignatureHelpProviderTests.cs b/src/EditorFeatures/CSharpTest/SignatureHelp/InvocationExpressionSignatureHelpProviderTests.cs index 6e676e6379bb9..bdad786acc55d 100644 --- a/src/EditorFeatures/CSharpTest/SignatureHelp/InvocationExpressionSignatureHelpProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/SignatureHelp/InvocationExpressionSignatureHelpProviderTests.cs @@ -2524,6 +2524,7 @@ void goo() expectedItems.Add(new SignatureHelpTestItem($"void C.M(object o)", currentParameterIndex: 0)); expectedItems.Add(new SignatureHelpTestItem($"void C.M(Action arg1, T arg2, bool flag)\r\n\r\n{string.Format(FeaturesResources._0_1, "Proj1", FeaturesResources.Available)}\r\n{string.Format(FeaturesResources._0_1, "Proj2", FeaturesResources.Not_Available)}\r\n\r\n{FeaturesResources.You_can_use_the_navigation_bar_to_switch_contexts}", currentParameterIndex: 0)); } + await VerifyItemWithReferenceWorkerAsync(markup, expectedItems, hideAdvancedMembers: false); } } diff --git a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs index 92f503e11e5c5..48b8c995906dd 100644 --- a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs @@ -64,6 +64,7 @@ private static void TestWorker( { snapshotSpans.Add(selection.ToSnapshotSpan(originalSnapshot)); } + view.SetMultiSelection(snapshotSpans); var undoHistoryRegistry = workspace.GetService(); diff --git a/src/EditorFeatures/CSharpTest/Wrapping/ParameterWrappingTests.cs b/src/EditorFeatures/CSharpTest/Wrapping/ParameterWrappingTests.cs index 387a414ac8235..e6f1d1c514d27 100644 --- a/src/EditorFeatures/CSharpTest/Wrapping/ParameterWrappingTests.cs +++ b/src/EditorFeatures/CSharpTest/Wrapping/ParameterWrappingTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.Wrapping; using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; @@ -923,5 +924,41 @@ void Local(C c, } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsWrapping)] + public async Task TestRecord_Semicolon() + { + await TestInRegularAndScript1Async( +"record R([||]int I, string S);", +@"record R(int I, + string S);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsWrapping)] + public async Task TestRecord_Braces() + { + await TestInRegularAndScript1Async( +"record R([||]int I, string S) { }", +@"record R(int I, + string S) { }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsWrapping)] + public async Task TestRecordStruct_Semicolon() + { + await TestInRegularAndScript1Async( +"record struct R([||]int I, string S);", +@"record struct R(int I, + string S);", new TestParameters(TestOptions.RegularPreview)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsWrapping)] + public async Task TestRecordStruct_Braces() + { + await TestInRegularAndScript1Async( +"record struct R([||]int I, string S) { }", +@"record struct R(int I, + string S) { }", new TestParameters(TestOptions.RegularPreview)); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs index 1687084899b56..67cd2e565fefc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs @@ -280,10 +280,18 @@ public async Task TestNotAfterSealed() public async Task TestNotAfterStatic() => await VerifyAbsenceAsync(@"static $$"); + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedStatic([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) + { + await VerifyAbsenceAsync(declarationKind + @" C { + static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedStatic() + public async Task TestAfterNestedStaticInInterface() { - await VerifyAbsenceAsync(@"class C { + await VerifyKeywordAsync(@"interface C { static $$"); } @@ -299,24 +307,27 @@ await VerifyKeywordAsync( public async Task TestNotAfterDelegate() => await VerifyAbsenceAsync(@"delegate $$"); - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedAbstract() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedAbstract([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { abstract $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedVirtual() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedVirtual([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { virtual $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedSealed() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedSealed([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { sealed $$"); } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs index b065e3d8da967..68389be670a4a 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs @@ -238,25 +238,113 @@ public async Task TestNotAfterStaticPublic() => await VerifyAbsenceAsync(@"static public $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterNestedStaticPublic() + public async Task TestAfterNestedStaticPublicInClass() { await VerifyKeywordAsync( @"class C { static public $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedStaticPublicInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + static public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedAbstractPublicInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + abstract public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedStaticAbstractPublicInInterface() + { + await VerifyKeywordAsync( +@"interface C { + static abstract public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedAbstractStaticPublicInInterface() + { + await VerifyKeywordAsync( +@"interface C { + abstract static public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedStaticAbstractInInterface() + { + await VerifyKeywordAsync( +@"interface C { + static abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedAbstractStaticInInterface() + { + await VerifyKeywordAsync( +@"interface C { + abstract static $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedStaticInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterPublicStatic() => await VerifyAbsenceAsync(@"public static $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterNestedPublicStatic() + public async Task TestAfterNestedPublicStaticInClass() { await VerifyKeywordAsync( @"class C { public static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedPublicStaticInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + public static $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedPublicAbstractInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + public abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedPublicStaticAbstractInInterface() + { + await VerifyKeywordAsync( +@"interface C { + public static abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedPublicAbstractStaticInInterface() + { + await VerifyKeywordAsync( +@"interface C { + public abstract static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterInvalidPublic() => await VerifyAbsenceAsync(@"virtual public $$"); @@ -313,35 +401,35 @@ await VerifyAbsenceAsync( } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedAbstract() + public async Task TestNotAfterNestedAbstractInClass() { await VerifyAbsenceAsync(@"class C { abstract $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedVirtual() + public async Task TestNotAfterNestedVirtualInClass() { await VerifyAbsenceAsync(@"class C { virtual $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedOverride() + public async Task TestNotAfterNestedOverrideInClass() { await VerifyAbsenceAsync(@"class C { override $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterNestedSealed() + public async Task TestNotAfterNestedSealedInClass() { await VerifyAbsenceAsync(@"class C { sealed $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedReadOnly() + public async Task TestNotAfterNestedReadOnlyInClass() { await VerifyAbsenceAsync(@"class C { readonly $$"); @@ -349,10 +437,59 @@ await VerifyAbsenceAsync(@"class C { [WorkItem(544102, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544102")] [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterUnsafeStaticPublic() + public async Task TestAfterNestedUnsafeStaticPublicInClass() { await VerifyKeywordAsync(@"class C { unsafe static public $$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedAbstractInInterface() + { + await VerifyAbsenceAsync(@"interface C { + abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedVirtualInInterface() + { + await VerifyAbsenceAsync(@"interface C { + virtual $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedOverrideInInterface() + { + await VerifyAbsenceAsync(@"interface C { + override $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedSealedInInterface() + { + await VerifyAbsenceAsync(@"interface C { + sealed $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedReadOnlyInInterface() + { + await VerifyAbsenceAsync(@"interface C { + readonly $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterUnsafeStaticAbstractInInterface() + { + await VerifyKeywordAsync(@"interface C { + unsafe static abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterExternStaticAbstractInInterface() + { + await VerifyAbsenceAsync(@"interface C { + extern static abstract $$"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs index dea0b522ee7f3..5967a4a667651 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs @@ -238,25 +238,113 @@ public async Task TestNotAfterStaticPublic() => await VerifyAbsenceAsync(@"static public $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterNestedStaticPublic() + public async Task TestAfterNestedStaticPublicInClass() { await VerifyKeywordAsync( @"class C { static public $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedStaticPublicInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + static public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedAbstractPublicInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + abstract public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedStaticAbstractPublicInInterface() + { + await VerifyKeywordAsync( +@"interface C { + static abstract public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedAbstractStaticPublicInInterface() + { + await VerifyKeywordAsync( +@"interface C { + abstract static public $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedStaticAbstractInInterface() + { + await VerifyKeywordAsync( +@"interface C { + static abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedAbstractStaticInInterface() + { + await VerifyKeywordAsync( +@"interface C { + abstract static $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedStaticInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterPublicStatic() => await VerifyAbsenceAsync(@"public static $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterNestedPublicStatic() + public async Task TestAfterNestedPublicStaticInClass() { await VerifyKeywordAsync( @"class C { public static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedPublicStaticInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + public static $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedPublicAbstractInInterface() + { + await VerifyAbsenceAsync( +@"interface C { + public abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedPublicStaticAbstractInInterface() + { + await VerifyKeywordAsync( +@"interface C { + public static abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterNestedPublicAbstractStaticInInterface() + { + await VerifyKeywordAsync( +@"interface C { + public abstract static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterInvalidPublic() => await VerifyAbsenceAsync(@"virtual public $$"); @@ -313,35 +401,35 @@ await VerifyAbsenceAsync( } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedAbstract() + public async Task TestNotAfterNestedAbstractInClass() { await VerifyAbsenceAsync(@"class C { abstract $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedVirtual() + public async Task TestNotAfterNestedVirtualInClass() { await VerifyAbsenceAsync(@"class C { virtual $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedOverride() + public async Task TestNotAfterNestedOverrideInClass() { await VerifyAbsenceAsync(@"class C { override $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterNestedSealed() + public async Task TestNotAfterNestedSealedInClass() { await VerifyAbsenceAsync(@"class C { sealed $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedReadOnly() + public async Task TestNotAfterNestedReadOnlyInClass() { await VerifyAbsenceAsync(@"class C { readonly $$"); @@ -349,10 +437,59 @@ await VerifyAbsenceAsync(@"class C { [WorkItem(544103, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544103")] [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterUnsafeStaticPublic() + public async Task TestAfterNestedUnsafeStaticPublicInClass() { await VerifyKeywordAsync(@"class C { unsafe static public $$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedAbstractInInterface() + { + await VerifyAbsenceAsync(@"interface C { + abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedVirtualInInterface() + { + await VerifyAbsenceAsync(@"interface C { + virtual $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedOverrideInInterface() + { + await VerifyAbsenceAsync(@"interface C { + override $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedSealedInInterface() + { + await VerifyAbsenceAsync(@"interface C { + sealed $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterNestedReadOnlyInInterface() + { + await VerifyAbsenceAsync(@"interface C { + readonly $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterUnsafeStaticAbstractInInterface() + { + await VerifyKeywordAsync(@"interface C { + unsafe static abstract $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterExternStaticAbstractInInterface() + { + await VerifyAbsenceAsync(@"interface C { + extern static abstract $$"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs index bcf70ccee66f2..be370faab5655 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs @@ -435,5 +435,12 @@ await VerifyKeywordAsync( @"class C { new $$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterReadonly() + { + // readonly record struct is allowed. + await VerifyKeywordAsync("readonly $$"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs index d92a732f94bb6..ec808ebd39fd3 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs @@ -280,10 +280,18 @@ public async Task TestNotAfterSealed() public async Task TestNotAfterStatic() => await VerifyAbsenceAsync(@"static $$"); + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedStatic([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) + { + await VerifyAbsenceAsync(declarationKind + @" C { + static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedStatic() + public async Task TestAfterNestedStaticInInterface() { - await VerifyAbsenceAsync(@"class C { + await VerifyKeywordAsync(@"interface C { static $$"); } @@ -299,17 +307,19 @@ await VerifyKeywordAsync( public async Task TestNotAfterDelegate() => await VerifyAbsenceAsync(@"delegate $$"); - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedAbstract() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedAbstract([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { abstract $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedVirtual() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedVirtual([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { virtual $$"); } @@ -321,10 +331,11 @@ await VerifyKeywordAsync( override $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedSealed() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedSealed([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { sealed $$"); } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs index 3c5b7e85e4f69..31603af3711b2 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs @@ -335,31 +335,49 @@ public async Task TestNotBetweenGlobalUsings_02() //await VerifyWorkerAsync(source, absent: true, Options.Script); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedAbstract() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedAbstract([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { abstract $$"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedVirtual() + public async Task TestAfterNestedAbstractInInterface() + { + await VerifyKeywordAsync(@"interface C { + abstract $$"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedVirtual([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { virtual $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedOverride() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedOverride([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { - await VerifyAbsenceAsync(@"class C { + await VerifyAbsenceAsync(declarationKind + @" C { override $$"); } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [CombinatorialData] + public async Task TestNotAfterNestedSealed([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) + { + await VerifyAbsenceAsync(declarationKind + @" C { + sealed $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotAfterNestedSealed() + public async Task TestAfterNestedSealedInInterface() { - await VerifyAbsenceAsync(@"class C { + await VerifyKeywordAsync(@"interface C { sealed $$"); } diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs index 433908df46f91..5006710ae5adb 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs @@ -81,6 +81,7 @@ private static async Task GatherSymbolsAsync(ISymbol symbol, Microsof { result[i++] = item; } + return result; } diff --git a/src/EditorFeatures/Core.Cocoa/Preview/PreviewPane.cs b/src/EditorFeatures/Core.Cocoa/Preview/PreviewPane.cs index 57d6587bb3621..ef6ac2fa67878 100644 --- a/src/EditorFeatures/Core.Cocoa/Preview/PreviewPane.cs +++ b/src/EditorFeatures/Core.Cocoa/Preview/PreviewPane.cs @@ -143,6 +143,7 @@ protected override void Dispose(bool disposing) _differenceViewerPreview?.Dispose(); _differenceViewerPreview = null; } + base.Dispose(disposing); } } diff --git a/src/EditorFeatures/Core.Wpf/Adornments/AbstractAdornmentManagerProvider.cs b/src/EditorFeatures/Core.Wpf/Adornments/AbstractAdornmentManagerProvider.cs index b14b99ceca4df..c6265c6c55bc8 100644 --- a/src/EditorFeatures/Core.Wpf/Adornments/AbstractAdornmentManagerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Adornments/AbstractAdornmentManagerProvider.cs @@ -18,23 +18,25 @@ internal abstract class AbstractAdornmentManagerProvider : IWpfTextViewCreationListener where TTag : GraphicsTag { - private readonly IThreadingContext _threadingContext; - private readonly IViewTagAggregatorFactoryService _tagAggregatorFactoryService; - private readonly IAsynchronousOperationListener _asyncListener; + protected readonly IThreadingContext ThreadingContext; + protected readonly IViewTagAggregatorFactoryService TagAggregatorFactoryService; + protected readonly IAsynchronousOperationListener AsyncListener; protected AbstractAdornmentManagerProvider( IThreadingContext threadingContext, IViewTagAggregatorFactoryService tagAggregatorFactoryService, IAsynchronousOperationListenerProvider listenerProvider) { - _threadingContext = threadingContext; - _tagAggregatorFactoryService = tagAggregatorFactoryService; - _asyncListener = listenerProvider.GetListener(this.FeatureAttributeName); + ThreadingContext = threadingContext; + TagAggregatorFactoryService = tagAggregatorFactoryService; + AsyncListener = listenerProvider.GetListener(this.FeatureAttributeName); } protected abstract string FeatureAttributeName { get; } protected abstract string AdornmentLayerName { get; } + protected abstract void CreateAdornmentManager(IWpfTextView textView); + public void TextViewCreated(IWpfTextView textView) { if (textView == null) @@ -47,8 +49,7 @@ public void TextViewCreated(IWpfTextView textView) return; } - // the manager keeps itself alive by listening to text view events. - AdornmentManager.Create(_threadingContext, textView, _tagAggregatorFactoryService, _asyncListener, AdornmentLayerName); + CreateAdornmentManager(textView); } } } diff --git a/src/EditorFeatures/Core.Wpf/Adornments/AdornmentManager.cs b/src/EditorFeatures/Core.Wpf/Adornments/AdornmentManager.cs index 1ffa5890307cf..b9f6b997a3848 100644 --- a/src/EditorFeatures/Core.Wpf/Adornments/AdornmentManager.cs +++ b/src/EditorFeatures/Core.Wpf/Adornments/AdornmentManager.cs @@ -22,42 +22,36 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Adornments /// /// UI manager for graphic overlay tags. These tags will simply paint something related to the text. /// - internal class AdornmentManager where T : GraphicsTag + internal abstract class AdornmentManager where T : GraphicsTag { private readonly object _invalidatedSpansLock = new object(); private readonly IThreadingContext _threadingContext; - /// View that created us. - private readonly IWpfTextView _textView; - - /// Layer where we draw adornments. - private readonly IAdornmentLayer _adornmentLayer; - - /// Aggregator that tells us where to draw. - private readonly ITagAggregator _tagAggregator; - /// Notification system about operations we do private readonly IAsynchronousOperationListener _asyncListener; /// Spans that are invalidated, and need to be removed from the layer.. private List _invalidatedSpans; - public static AdornmentManager Create( - IThreadingContext threadingContext, - IWpfTextView textView, - IViewTagAggregatorFactoryService aggregatorService, - IAsynchronousOperationListener asyncListener, - string adornmentLayerName) - { - Contract.ThrowIfNull(threadingContext); - Contract.ThrowIfNull(textView); - Contract.ThrowIfNull(aggregatorService); - Contract.ThrowIfNull(adornmentLayerName); - Contract.ThrowIfNull(asyncListener); + /// View that created us. + protected IWpfTextView TextView { get; } - return new AdornmentManager(threadingContext, textView, aggregatorService, asyncListener, adornmentLayerName); - } + /// Layer where we draw adornments. + protected IAdornmentLayer AdornmentLayer { get; } + + /// Aggregator that tells us where to draw. + protected ITagAggregator TagAggregator { get; } + + /// + /// MUST BE CALLED ON UI THREAD!!!! This method touches WPF. + /// + /// This is where we apply visuals to the text. + /// + /// It happens when another region of the view becomes visible or there is a change in tags. + /// For us the end result is the same - get tags from tagger and update visuals correspondingly. + /// + protected abstract void UpdateSpans_CallOnlyOnUIThread(NormalizedSnapshotSpanCollection changedSpanCollection, bool removeOldTags); internal AdornmentManager( IThreadingContext threadingContext, @@ -73,8 +67,8 @@ internal AdornmentManager( Contract.ThrowIfNull(asyncListener); _threadingContext = threadingContext; - _textView = textView; - _adornmentLayer = textView.GetAdornmentLayer(adornmentLayerName); + TextView = textView; + AdornmentLayer = textView.GetAdornmentLayer(adornmentLayerName); textView.LayoutChanged += OnLayoutChanged; _asyncListener = asyncListener; @@ -82,20 +76,20 @@ internal AdornmentManager( Contract.ThrowIfFalse(textView.VisualElement.Dispatcher.CheckAccess()); textView.Closed += OnTextViewClosed; - _tagAggregator = tagAggregatorFactoryService.CreateTagAggregator(textView); + TagAggregator = tagAggregatorFactoryService.CreateTagAggregator(textView); - _tagAggregator.TagsChanged += OnTagsChanged; + TagAggregator.TagsChanged += OnTagsChanged; } private void OnTextViewClosed(object sender, System.EventArgs e) { // release the aggregator - _tagAggregator.TagsChanged -= OnTagsChanged; - _tagAggregator.Dispose(); + TagAggregator.TagsChanged -= OnTagsChanged; + TagAggregator.Dispose(); // unhook from view - _textView.Closed -= OnTextViewClosed; - _textView.LayoutChanged -= OnLayoutChanged; + TextView.Closed -= OnTextViewClosed; + TextView.LayoutChanged -= OnLayoutChanged; // At this point, this object should be available for garbage collection. } @@ -110,10 +104,10 @@ private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) using (_asyncListener.BeginAsyncOperation(GetType() + ".OnLayoutChanged")) { // Make sure we're on the UI thread. - Contract.ThrowIfFalse(_textView.VisualElement.Dispatcher.CheckAccess()); + Contract.ThrowIfFalse(TextView.VisualElement.Dispatcher.CheckAccess()); var reformattedSpans = e.NewOrReformattedSpans; - var viewSnapshot = _textView.TextSnapshot; + var viewSnapshot = TextView.TextSnapshot; // No need to remove tags as these spans are reformatted anyways. UpdateSpans_CallOnlyOnUIThread(reformattedSpans, removeOldTags: false); @@ -182,7 +176,7 @@ private void OnTagsChanged(object sender, TagsChangedEventArgs e) if (needToScheduleUpdate) { // schedule an update - _threadingContext.JoinableTaskFactory.WithPriority(_textView.VisualElement.Dispatcher, DispatcherPriority.Render).RunAsync(async () => + _threadingContext.JoinableTaskFactory.WithPriority(TextView.VisualElement.Dispatcher, DispatcherPriority.Render).RunAsync(async () => { using (_asyncListener.BeginAsyncOperation(GetType() + ".OnTagsChanged.2")) { @@ -205,7 +199,7 @@ private void UpdateInvalidSpans() using (Logger.LogBlock(FunctionId.Tagger_AdornmentManager_UpdateInvalidSpans, CancellationToken.None)) { // this method should only run on UI thread as we do WPF here. - Contract.ThrowIfFalse(_textView.VisualElement.Dispatcher.CheckAccess()); + Contract.ThrowIfFalse(TextView.VisualElement.Dispatcher.CheckAccess()); List invalidated; lock (_invalidatedSpansLock) @@ -214,118 +208,25 @@ private void UpdateInvalidSpans() _invalidatedSpans = null; } - if (_textView.IsClosed) + if (TextView.IsClosed) { return; // already closed } if (invalidated != null) { - var viewSnapshot = _textView.TextSnapshot; + var viewSnapshot = TextView.TextSnapshot; var invalidatedNormalized = TranslateAndNormalize(invalidated, viewSnapshot); UpdateSpans_CallOnlyOnUIThread(invalidatedNormalized, removeOldTags: true); } } } - /// - /// MUST BE CALLED ON UI THREAD!!!! This method touches WPF. - /// - /// This is where we apply visuals to the text. - /// - /// It happens when another region of the view becomes visible or there is a change in tags. - /// For us the end result is the same - get tags from tagger and update visuals correspondingly. - /// - private void UpdateSpans_CallOnlyOnUIThread(NormalizedSnapshotSpanCollection changedSpanCollection, bool removeOldTags) - { - Contract.ThrowIfNull(changedSpanCollection); - - // this method should only run on UI thread as we do WPF here. - Contract.ThrowIfFalse(_textView.VisualElement.Dispatcher.CheckAccess()); - - var viewSnapshot = _textView.TextSnapshot; - var visualSnapshot = _textView.VisualSnapshot; - - var viewLines = _textView.TextViewLines; - if (viewLines == null || viewLines.Count == 0) - { - return; // nothing to draw on - } - - // removing is a separate pass from adding so that new stuff is not removed. - if (removeOldTags) - { - foreach (var changedSpan in changedSpanCollection) - { - // is there any effect on the view? - if (viewLines.IntersectsBufferSpan(changedSpan)) - { - _adornmentLayer.RemoveAdornmentsByVisualSpan(changedSpan); - } - } - } - - foreach (var changedSpan in changedSpanCollection) - { - // is there any effect on the view? - if (!viewLines.IntersectsBufferSpan(changedSpan)) - { - continue; - } - - var tagSpans = _tagAggregator.GetTags(changedSpan); - foreach (var tagMappingSpan in tagSpans) - { - // We don't want to draw line separators if they would intersect a collapsed outlining - // region. So we test if we can map the start of the line separator up to our visual - // snapshot. If we can't, then we just skip it. - var point = tagMappingSpan.Span.Start.GetPoint(changedSpan.Snapshot, PositionAffinity.Predecessor); - if (point == null) - { - continue; - } - - var mappedPoint = _textView.BufferGraph.MapUpToSnapshot( - point.Value, PointTrackingMode.Negative, PositionAffinity.Predecessor, _textView.VisualSnapshot); - if (mappedPoint == null) - { - continue; - } - - if (!TryMapToSingleSnapshotSpan(tagMappingSpan.Span, viewSnapshot, out var span)) - { - continue; - } - - if (!viewLines.IntersectsBufferSpan(span)) - { - // span is outside of the view so we will not get geometry for it, but may - // spent a lot of time trying. - continue; - } - - // add the visual to the adornment layer. - var geometry = viewLines.GetMarkerGeometry(span); - if (geometry != null) - { - var tag = tagMappingSpan.Tag; - var graphicsResult = tag.GetGraphics(_textView, geometry); - _adornmentLayer.AddAdornment( - behavior: AdornmentPositioningBehavior.TextRelative, - visualSpan: span, - tag: tag, - adornment: graphicsResult.VisualElement, - removedCallback: delegate { graphicsResult.Dispose(); }); - } - } - } - } - // Map the mapping span to the visual snapshot. note that as a result of projection // topology, originally single span may be mapped into several spans. Visual adornments do // not make much sense on disjoint spans. We will not decorate spans that could not make it // in one piece. - private static bool TryMapToSingleSnapshotSpan(IMappingSpan mappingSpan, ITextSnapshot viewSnapshot, out SnapshotSpan span) + protected static bool TryMapToSingleSnapshotSpan(IMappingSpan mappingSpan, ITextSnapshot viewSnapshot, out SnapshotSpan span) { // IMappingSpan.GetSpans is a surprisingly expensive function that allocates multiple // lists and collection if the view buffer is same as anchor we could just map the diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs index 48fd5dff6c9a1..ea6d47700ed15 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs @@ -14,6 +14,7 @@ using System.Windows.Input; using System.Windows.Media; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.InlineHints; using Microsoft.CodeAnalysis.PooledObjects; @@ -94,8 +95,8 @@ public async Task> CreateDescriptionAsync(Cancellati var taggedText = await _hint.GetDescriptionAsync(document, cancellationToken).ConfigureAwait(false); if (!taggedText.IsDefaultOrEmpty) { - return Implementation.IntelliSense.Helpers.BuildInteractiveTextElements( - taggedText, document, _threadingContext, _streamingPresenter); + var context = new IntellisenseQuickInfoBuilderContext(document, _threadingContext, _streamingPresenter); + return Implementation.IntelliSense.Helpers.BuildInteractiveTextElements(taggedText, context); } } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractivePasteCommandHandler.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractivePasteCommandHandler.cs index cd325079749bc..48dbd5836eb5e 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractivePasteCommandHandler.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractivePasteCommandHandler.cs @@ -131,6 +131,7 @@ private void PasteInteractiveFormat(ITextView textView) { editorOperations.InsertText(text); } + editorOperations.AddAfterTextBufferChangePrimitive(); transaction.Complete(); } @@ -147,6 +148,7 @@ private static bool HasNonWhiteSpaceCharacter(ITextSnapshotLine line) return true; } } + return false; } diff --git a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManager.cs b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManager.cs new file mode 100644 index 0000000000000..772406ebfbf07 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManager.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Editor.Implementation.Adornments; +using Microsoft.CodeAnalysis.Editor.Implementation.LineSeparators; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.LineSeparators +{ + internal class LineSeparatorAdornmentManager : AdornmentManager + { + public LineSeparatorAdornmentManager(IThreadingContext threadingContext, IWpfTextView textView, + IViewTagAggregatorFactoryService tagAggregatorFactoryService, IAsynchronousOperationListener asyncListener, string adornmentLayerName) + : base(threadingContext, textView, tagAggregatorFactoryService, asyncListener, adornmentLayerName) + { + } + + /// + /// MUST BE CALLED ON UI THREAD!!!! This method touches WPF. + /// + /// This is where we apply visuals to the text. + /// + /// It happens when another region of the view becomes visible or there is a change in tags. + /// For us the end result is the same - get tags from tagger and update visuals correspondingly. + /// + protected override void UpdateSpans_CallOnlyOnUIThread(NormalizedSnapshotSpanCollection changedSpanCollection, bool removeOldTags) + { + Contract.ThrowIfNull(changedSpanCollection); + + // this method should only run on UI thread as we do WPF here. + Contract.ThrowIfFalse(TextView.VisualElement.Dispatcher.CheckAccess()); + + var viewSnapshot = TextView.TextSnapshot; + var visualSnapshot = TextView.VisualSnapshot; + + var viewLines = TextView.TextViewLines; + if (viewLines == null || viewLines.Count == 0) + { + return; // nothing to draw on + } + + // removing is a separate pass from adding so that new stuff is not removed. + if (removeOldTags) + { + foreach (var changedSpan in changedSpanCollection) + { + // is there any effect on the view? + if (viewLines.IntersectsBufferSpan(changedSpan)) + { + AdornmentLayer.RemoveAdornmentsByVisualSpan(changedSpan); + } + } + } + + foreach (var changedSpan in changedSpanCollection) + { + // is there any effect on the view? + if (!viewLines.IntersectsBufferSpan(changedSpan)) + { + continue; + } + + var tagSpans = TagAggregator.GetTags(changedSpan); + foreach (var tagMappingSpan in tagSpans) + { + // We don't want to draw line separators if they would intersect a collapsed outlining + // region. So we test if we can map the start of the line separator up to our visual + // snapshot. If we can't, then we just skip it. + var point = tagMappingSpan.Span.Start.GetPoint(changedSpan.Snapshot, PositionAffinity.Predecessor); + if (point == null) + { + continue; + } + + var mappedPoint = TextView.BufferGraph.MapUpToSnapshot( + point.Value, PointTrackingMode.Negative, PositionAffinity.Predecessor, TextView.VisualSnapshot); + if (mappedPoint == null) + { + continue; + } + + if (!TryMapToSingleSnapshotSpan(tagMappingSpan.Span, viewSnapshot, out var span)) + { + continue; + } + + if (!viewLines.IntersectsBufferSpan(span)) + { + // span is outside of the view so we will not get geometry for it, but may + // spent a lot of time trying. + continue; + } + + // add the visual to the adornment layer. + var geometry = viewLines.GetMarkerGeometry(span); + if (geometry != null) + { + var tag = tagMappingSpan.Tag; + var graphicsResult = tag.GetGraphics(TextView, geometry); + AdornmentLayer.AddAdornment( + behavior: AdornmentPositioningBehavior.TextRelative, + visualSpan: span, + tag: tag, + adornment: graphicsResult.VisualElement, + removedCallback: delegate { graphicsResult.Dispose(); }); + } + } + } + } + } +} diff --git a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManagerProvider.cs b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManagerProvider.cs index 04596b37595cf..c1852a17a3751 100644 --- a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManagerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorAdornmentManagerProvider.cs @@ -7,6 +7,9 @@ using System; using System.ComponentModel.Composition; using Microsoft.CodeAnalysis.Editor.Implementation.Adornments; +using Microsoft.CodeAnalysis.Editor.LineSeparators; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -49,5 +52,11 @@ public LineSeparatorAdornmentManagerProvider( protected override string FeatureAttributeName => FeatureAttribute.LineSeparators; protected override string AdornmentLayerName => LayerName; + + protected override void CreateAdornmentManager(IWpfTextView textView) + { + // the manager keeps itself alive by listening to text view events. + _ = new LineSeparatorAdornmentManager(ThreadingContext, textView, TagAggregatorFactoryService, AsyncListener, AdornmentLayerName); + } } } diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs index 739189b0e9308..77466cd692963 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs @@ -53,15 +53,7 @@ private ReadOnlyCollection CreateDescriptionItems() } var sourceText = document.GetTextSynchronously(CancellationToken.None); - var item = _searchResult.NavigableItem; - var spanStart = item.SourceSpan.Start; - if (item.IsStale && spanStart > sourceText.Length) - { - // in the case of a stale item, the span may be out of bounds of the document. Cap - // us to the end of the document as that's where we're going to navigate the user - // to. - spanStart = sourceText.Length; - } + var span = NavigateToUtilities.GetBoundedSpan(_searchResult.NavigableItem, sourceText); var items = new List { @@ -79,7 +71,7 @@ private ReadOnlyCollection CreateDescriptionItems() new ReadOnlyCollection( new[] { new DescriptionRun("Line:", bold: true) }), new ReadOnlyCollection( - new[] { new DescriptionRun((sourceText.Lines.IndexOf(spanStart) + 1).ToString()) })) + new[] { new DescriptionRun((sourceText.Lines.IndexOf(span.Start) + 1).ToString()) })) }; var summary = _searchResult.Summary; diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs index 7e11f5c6f471e..fbf3a3b77e44c 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs @@ -41,22 +41,7 @@ public NavigateToItemProvider( ISet INavigateToItemProvider2.KindsProvided => KindsProvided; public ImmutableHashSet KindsProvided - { - get - { - var result = ImmutableHashSet.Create(StringComparer.Ordinal); - foreach (var project in _workspace.CurrentSolution.Projects) - { - var navigateToSearchService = project.GetLanguageService(); - if (navigateToSearchService != null) - { - result = result.Union(navigateToSearchService.KindsProvided); - } - } - - return result; - } - } + => NavigateToUtilities.GetKindsProvided(_workspace.CurrentSolution); public bool CanFilter { diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs index 8dd3316a1bfd4..13e36727a41c0 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs @@ -146,6 +146,7 @@ private static SignatureHelpItem GetSelectedItem(Model currentModel, SignatureHe return userSelectedItem; } } + userSelected = false; // If the provider specified a selected item, then pick that one. diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs index 144b853d9abd9..99cdba9265e4d 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs @@ -79,6 +79,7 @@ private static ISettingsProviderFactory GetOptionsProviderFactory(Workspac { TryAddProviderForLanguage(LanguageNames.CSharp, workspace, providers); } + if (supportsVisualBasic) { TryAddProviderForLanguage(LanguageNames.VisualBasic, workspace, providers); diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.cs index 8856236da98cc..c4a4abf69d317 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.cs @@ -29,6 +29,7 @@ public EnumCodeStyleSettingBase(string description, { throw new InvalidOperationException("Values and descriptions must have matching number of elements"); } + _enumValues = enumValues; _valueDescriptions = valueDescriptions; Category = category; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs b/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs index fe06a29a6b5c8..46418543677ce 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs @@ -49,6 +49,7 @@ static bool ContainsPath(DirectoryInfo givenPath, DirectoryInfo projectPath) { return true; } + projectPath = projectPath.Parent; } diff --git a/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs b/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs index 545e53524b7d7..531132d451939 100644 --- a/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs +++ b/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs @@ -178,5 +178,10 @@ internal static class PredefinedCommandHandlerNames /// Command handler name for Edit and Continue file save handler. /// public const string EditAndContinueFileSave = "Edit and Continue Save File Handler"; + + /// + /// Command handler name for showing the Value Tracking tool window. + /// + public const string ShowValueTracking = "Show Value Tracking"; } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarController.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarController.cs deleted file mode 100644 index 99ef6d783a404..0000000000000 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarController.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CodeAnalysis.Editor -{ - internal interface INavigationBarController - { - void Disconnect(); - - void SetWorkspace(Workspace? newWorkspace); - } -} diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarControllerFactoryService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarControllerFactoryService.cs index f5516183c958b..0ff6f947d5f33 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarControllerFactoryService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarControllerFactoryService.cs @@ -4,12 +4,13 @@ #nullable disable +using System; using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor { internal interface INavigationBarControllerFactoryService { - INavigationBarController CreateController(INavigationBarPresenter presenter, ITextBuffer textBuffer); + IDisposable CreateController(INavigationBarPresenter presenter, ITextBuffer textBuffer); } } diff --git a/src/EditorFeatures/Core/ExternalAccess/IntelliCode/Api/IIntelliCodeArgumentDefaultsSource.cs b/src/EditorFeatures/Core/ExternalAccess/IntelliCode/Api/IIntelliCodeArgumentDefaultsSource.cs new file mode 100644 index 0000000000000..7ac6a9ac08fe5 --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/IntelliCode/Api/IIntelliCodeArgumentDefaultsSource.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.IntelliCode.Api +{ + /// + /// Provides a list of possible default arguments for method calls. + /// + /// + /// This is a MEF component and should be exported with and attributes + /// and optional and attributes. + /// An instance of is selected + /// first by matching ContentType with content type of the , and then by order. + /// Only one is used in a given view. + /// + /// Only one will used for any given . The sources are + /// ordered by the Order attribute. The first source (if any) that satisfies the ContentType and TextViewRoles + /// attributes will be the source used to provide defaults. + /// + /// + /// + /// [Export(typeof(IIntelliCodeArgumentDefaultsSource))] + /// [Name(nameof(IntelliCodeArgumentDefaultsSource))] + /// [ContentType("text")] + /// [TextViewRoles(PredefinedTextViewRoles.Editable)] + /// [Order(Before = "OtherCompletionDefaultsSource")] + /// public class IntelliCodeArgumentDefaultsSource : IIntelliCodeArgumentDefaultsSource + /// + /// + /// + internal interface IIntelliCodeArgumentDefaultsSource + { + /// + /// Gets a list of possible default arguments for a method signature. + /// + /// View for which the defaults are desired. + /// A list of possible default arguments for a method signature. + /// + /// The returned value will always be in the form of a "complete" set of arguments, including the leading and trailing parenthesis. + /// For example: + /// + /// () + /// (args[0]) + /// (args.Length) + /// (value: args.Length) + /// + /// + /// Some of the proposals may be syntactically/semantically invalid (and can be ignored by the caller). + /// + Task> GetArgumentDefaultsAsync(ITextView view); + } +} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFormattingInteractionService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFormattingInteractionService.cs index bdcff8b3229bc..8b6f154e38c29 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFormattingInteractionService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFormattingInteractionService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFormattingInteractionService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFormattingInteractionService.cs index d4b2ab1481110..6a6bb7990ca23 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFormattingInteractionService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFormattingInteractionService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs b/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs index 5abc902357394..1a16c9bd21c35 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification @@ -86,7 +87,11 @@ private void OnEventSourceChanged(object? sender, TaggerEventArgs args) var token = _asyncListener.BeginAsyncOperation(nameof(OnEventSourceChanged)); var task = Task.Run(async () => { - await Task.Delay(500, cancellationToken).ConfigureAwait(false); + // Support cancellation without throwing + await _asyncListener.Delay(TimeSpan.FromMilliseconds(500), cancellationToken).NoThrowAwaitable(captureContext: false); + if (cancellationToken.IsCancellationRequested) + return; + await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); this.Changed?.Invoke(this, new TaggerEventArgs()); }, cancellationToken); diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index d4d083cd5eaca..ef3376af8d8d1 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -265,7 +265,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) /// the editor. Calls to are serialized by /// so we don't need to worry about multiple calls to this happening concurrently. /// - private async Task ProcessChangesAsync(ImmutableArray snapshots, CancellationToken cancellationToken) + private async ValueTask ProcessChangesAsync(ImmutableArray snapshots, CancellationToken cancellationToken) { // We have potentially heard about several changes to the subject buffer. However // we only need to process the latest once. diff --git a/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs b/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs index 067af4895568f..a7c7efc630646 100644 --- a/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs +++ b/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Undo; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Notification; @@ -134,7 +135,7 @@ public bool Apply( var oldSolution = workspace.CurrentSolution; - bool applied; + var applied = false; // Determine if we're making a simple text edit to a single file or not. // If we're not, then we need to make a linked global undo to wrap the @@ -152,7 +153,14 @@ public bool Apply( using (workspace.Services.GetService().RegisterUndoTransaction(text, title)) { - applied = operations.Single().TryApply(workspace, progressTracker, cancellationToken); + try + { + applied = operations.Single().TryApply(workspace, progressTracker, cancellationToken); + } + catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken)) + { + throw ExceptionUtilities.Unreachable; + } } } else @@ -169,9 +177,16 @@ public bool Apply( transaction.AddDocument(fromDocument.Id); } - applied = ProcessOperations( - workspace, operations, progressTracker, - cancellationToken); + try + { + applied = ProcessOperations( + workspace, operations, progressTracker, + cancellationToken); + } + catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken)) + { + throw ExceptionUtilities.Unreachable; + } transaction.Commit(); } diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs deleted file mode 100644 index a8c80e2830e9b..0000000000000 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.VisualStudio.Utilities; -using Roslyn.Utilities; -using VSCommanding = Microsoft.VisualStudio.Commanding; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue -{ - [Export] - [Export(typeof(VSCommanding.ICommandHandler))] - [ContentType(ContentTypeNames.RoslynContentType)] - [Name(PredefinedCommandHandlerNames.EditAndContinueFileSave)] - internal sealed class EditAndContinueSaveFileCommandHandler : IChainedCommandHandler - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public EditAndContinueSaveFileCommandHandler() - { - } - - public string DisplayName => PredefinedCommandHandlerNames.EditAndContinueFileSave; - - void IChainedCommandHandler.ExecuteCommand(SaveCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) - { - var textContainer = args.SubjectBuffer.AsTextContainer(); - - if (Workspace.TryGetWorkspace(textContainer, out var workspace)) - { - var documentId = workspace.GetDocumentIdInCurrentContext(textContainer); - if (documentId != null) - { - // ignoring source-generated files since they shouldn't be modified and saved: - var currentDocument = workspace.CurrentSolution.GetDocument(documentId); - if (currentDocument != null) - { - var proxy = new RemoteEditAndContinueServiceProxy(workspace); - - // fire and forget - _ = Task.Run(() => proxy.OnSourceFileUpdatedAsync(currentDocument, CancellationToken.None)).ReportNonFatalErrorAsync(); - } - } - } - - nextCommandHandler(); - } - - public VSCommanding.CommandState GetCommandState(SaveCommandArgs args, Func nextCommandHandler) - => nextCommandHandler(); - } -} - diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs index cc279135ac9bd..3cade421d1aa6 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs @@ -229,6 +229,7 @@ private void InitializeOpenBuffers(SnapshotSpan triggerSpan) FatalError.ReportAndCatch(new NullTextBufferException(document, text)); continue; } + Contract.ThrowIfNull(textSnapshot.TextBuffer); openBuffers.Add(textSnapshot.TextBuffer); diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs index d8069ec1f6559..bf42006821e29 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Experiments; @@ -373,7 +374,8 @@ private bool TryInvokeSnippetCompletion( var description = await service.GetDescriptionAsync(document, roslynItem, cancellationToken).ConfigureAwait(false); - var elements = IntelliSense.Helpers.BuildInteractiveTextElements(description.TaggedParts, document, ThreadingContext, _streamingPresenter).ToArray(); + var context = new IntellisenseQuickInfoBuilderContext(document, ThreadingContext, _streamingPresenter); + var elements = IntelliSense.Helpers.BuildInteractiveTextElements(description.TaggedParts, context).ToArray(); if (elements.Length == 0) { return new ClassifiedTextElement(); diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/Helpers.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/Helpers.cs index 3373f45cb33f9..ffce116fbf189 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/Helpers.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/Helpers.cs @@ -144,13 +144,6 @@ internal static bool IsStandardCommitCharacter(char c) internal static bool TryGetInitialTriggerLocation(EditorAsyncCompletion.IAsyncCompletionSession session, out SnapshotPoint initialTriggerLocation) => session.Properties.TryGetProperty(CompletionSource.TriggerLocation, out initialTriggerLocation); - // This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items. - // We expect that Editor will introduce this support and we will get rid of relying on the "★" then. - // We check both the display text and the display text prefix to account for IntelliCode item providers - // that may be using the prefix to include the ★. - internal static bool IsPreferredItem(this RoslynCompletionItem completionItem) - => completionItem.DisplayText.StartsWith("★") || completionItem.DisplayTextPrefix.StartsWith("★"); - // This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items. // We expect that Editor will introduce this support and we will get rid of relying on the "★" then. internal static bool IsPreferredItem(this VSCompletionItem completionItem) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs index 2180361d30d65..c8d8a09ae3939 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs @@ -84,7 +84,7 @@ public Task> SortCompletionListAsync( // Set the size of pool to 1 because we don't expect UpdateCompletionListAsync to be // called concurrently, which essentially makes the pooled list a singleton, // but we still use ObjectPool for concurrency handling just to be robust. - private static readonly ObjectPool> s_listOfMatchResultPool + private static readonly ObjectPool>> s_listOfMatchResultPool = new(factory: () => new(), size: 1); private FilteredCompletionModel? UpdateCompletionList( @@ -208,10 +208,11 @@ private static readonly ObjectPool> s_listOfMatchResultPool filterReason, _recentItemsManager.RecentItems, highlightMatchingPortions: highlightMatchingPortions, - ref currentIndex, + currentIndex, out var matchResult)) { initialListOfItemsToBeIncluded.Add(matchResult); + currentIndex++; } } @@ -224,7 +225,7 @@ private static readonly ObjectPool> s_listOfMatchResultPool // Note that we want to preserve the original alphabetical order for items with same pattern match score, // but `List.Sort` isn't stable. Therefore we have to add a monotonically increasing integer // to `MatchResult` to achieve this. - initialListOfItemsToBeIncluded.Sort(MatchResult.SortingComparer); + initialListOfItemsToBeIncluded.Sort(MatchResult.SortingComparer); var showCompletionItemFilters = options?.GetOption(CompletionOptions.ShowCompletionItemFilters, document?.Project.Language) ?? true; @@ -235,7 +236,7 @@ private static readonly ObjectPool> s_listOfMatchResultPool // If this was deletion, then we control the entire behavior of deletion ourselves. if (initialRoslynTriggerKind == CompletionTriggerKind.Deletion) { - return HandleDeletionTrigger(data.Trigger.Reason, initialListOfItemsToBeIncluded, filterText, updatedFilters, hasSuggestedItemOptions); + return HandleDeletionTrigger(reason, initialListOfItemsToBeIncluded, filterText, updatedFilters, hasSuggestedItemOptions, highlightMatchingPortions, completionHelper); } Func, string, ImmutableArray> filterMethod; @@ -255,7 +256,9 @@ private static readonly ObjectPool> s_listOfMatchResultPool filterReason, data.Trigger.Character, initialListOfItemsToBeIncluded, - hasSuggestedItemOptions); + hasSuggestedItemOptions, + highlightMatchingPortions, + completionHelper); } finally { @@ -310,8 +313,10 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT ImmutableArray filters, CompletionFilterReason filterReason, char typeChar, - List itemsInList, - bool hasSuggestedItemOptions) + List> itemsInList, + bool hasSuggestedItemOptions, + bool highlightMatchingPortions, + CompletionHelper completionHelper) { // Not deletion. Defer to the language to decide which item it thinks best // matches the text typed so far. @@ -324,7 +329,7 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT int selectedItemIndex; VSCompletionItem? uniqueItem = null; - MatchResult bestOrFirstMatchResult; + MatchResult bestOrFirstMatchResult; if (chosenItems.Length == 0) { @@ -367,7 +372,7 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT if (deduplicatedListCount == 1 && filterText.Length > 0) { - uniqueItem = itemsInList[selectedItemIndex].VSCompletionItem; + uniqueItem = itemsInList[selectedItemIndex].EditorCompletionItem; } } @@ -390,16 +395,18 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT var updateSelectionHint = isHardSelection ? UpdateSelectionHint.Selected : UpdateSelectionHint.SoftSelected; return new FilteredCompletionModel( - GetHighlightedList(itemsInList), selectedItemIndex, filters, + GetHighlightedList(itemsInList, filterText, highlightMatchingPortions, completionHelper), selectedItemIndex, filters, updateSelectionHint, centerSelection: true, uniqueItem); } private static FilteredCompletionModel? HandleDeletionTrigger( CompletionTriggerReason filterTriggerKind, - List matchResults, + List> matchResults, string filterText, ImmutableArray filters, - bool hasSuggestedItemOptions) + bool hasSuggestedItemOptions, + bool highlightMatchingSpans, + CompletionHelper completionHelper) { var matchingItems = matchResults.Where(r => r.MatchedFilterText); if (filterTriggerKind == CompletionTriggerReason.Insertion && @@ -414,7 +421,7 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT return null; } - MatchResult? bestMatchResult = null; + MatchResult? bestMatchResult = null; var moreThanOneMatchWithSamePriority = false; foreach (var currentMatchResult in matchingItems) { @@ -453,7 +460,7 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT // text that originally appeared before the dot // * deleting through a word from the end keeps that word selected // This also preserves the behavior the VB had through Dev12. - hardSelect = !hasSuggestedItemOptions && bestMatchResult.Value.VSCompletionItem.FilterText.StartsWith(filterText, StringComparison.CurrentCultureIgnoreCase); + hardSelect = !hasSuggestedItemOptions && bestMatchResult.Value.EditorCompletionItem.FilterText.StartsWith(filterText, StringComparison.CurrentCultureIgnoreCase); index = matchResults.IndexOf(bestMatchResult.Value); } else @@ -463,15 +470,56 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT } return new FilteredCompletionModel( - GetHighlightedList(matchResults), index, filters, + GetHighlightedList(matchResults, filterText, highlightMatchingSpans, completionHelper), index, filters, hardSelect ? UpdateSelectionHint.Selected : UpdateSelectionHint.SoftSelected, centerSelection: true, - uniqueItem: moreThanOneMatchWithSamePriority ? null : bestMatchResult.GetValueOrDefault().VSCompletionItem); + uniqueItem: moreThanOneMatchWithSamePriority ? null : bestMatchResult.GetValueOrDefault().EditorCompletionItem); } - private static ImmutableArray GetHighlightedList(List matchResults) - => matchResults.SelectAsArray(matchResult => - new CompletionItemWithHighlight(matchResult.VSCompletionItem, matchResult.HighlightedSpans)); + private static ImmutableArray GetHighlightedList( + List> matchResults, + string filterText, + bool highlightMatchingPortions, + CompletionHelper completionHelper) + { + return matchResults.SelectAsArray(matchResult => + { + var highlightedSpans = GetHighlightedSpans(matchResult, completionHelper, filterText, highlightMatchingPortions); + return new CompletionItemWithHighlight(matchResult.EditorCompletionItem, highlightedSpans); + }); + + static ImmutableArray GetHighlightedSpans( + MatchResult matchResult, + CompletionHelper completionHelper, + string filterText, + bool highlightMatchingPortions) + { + if (highlightMatchingPortions) + { + if (matchResult.RoslynCompletionItem.HasDifferentFilterText) + { + // The PatternMatch in MatchResult is calculated based on Roslyn item's FilterText, + // which can be used to calculate highlighted span for VSCompletion item's DisplayText w/o doing the matching again. + // However, if the Roslyn item's FilterText is different from its DisplayText, + // we need to do the match against the display text of the VS item directly to get the highlighted spans. + return completionHelper.GetHighlightedSpans( + matchResult.EditorCompletionItem.DisplayText, filterText, CultureInfo.CurrentCulture).SelectAsArray(s => s.ToSpan()); + } + + var patternMatch = matchResult.PatternMatch; + if (patternMatch.HasValue) + { + // Since VS item's display text is created as Prefix + DisplayText + Suffix, + // we can calculate the highlighted span by adding an offset that is the length of the Prefix. + return patternMatch.Value.MatchedSpans.SelectAsArray(s_highlightSpanGetter, matchResult.RoslynCompletionItem); + } + } + + // If there's no match for Roslyn item's filter text which is identical to its display text, + // then we can safely assume there'd be no matching to VS item's display text. + return ImmutableArray.Empty; + } + } private static FilteredCompletionModel? HandleAllItemsFilteredOut( CompletionTriggerReason triggerReason, @@ -504,12 +552,12 @@ private static ImmutableArray GetHighlightedList(Li } private static ImmutableArray GetUpdatedFilters( - List filteredList, + List> filteredList, ImmutableArray filters) { // See which filters might be enabled based on the typed code using var _ = PooledHashSet.GetInstance(out var textFilteredFilters); - textFilteredFilters.AddRange(filteredList.SelectMany(n => n.VSCompletionItem.Filters)); + textFilteredFilters.AddRange(filteredList.SelectMany(n => n.EditorCompletionItem.Filters)); // When no items are available for a given filter, it becomes unavailable. // Expanders always appear available as long as it's presented. @@ -584,21 +632,6 @@ private static RoslynCompletionItem GetOrAddRoslynCompletionItem(VSCompletionIte return roslynItem; } - // If the item didn't match the filter text, we still keep it in the list - // if one of two things is true: - // 1. The user has typed nothing or only typed a single character. In this case they might - // have just typed the character to get completion. Filtering out items - // here is not desirable. - // - // 2. They brought up completion with ctrl-j or through deletion. In these - // cases we just always keep all the items in the list. - private static bool KeepAllItemsInTheList(CompletionTriggerKind initialTriggerKind, string filterText) - { - return filterText.Length <= 1 || - initialTriggerKind == CompletionTriggerKind.Invoke || - initialTriggerKind == CompletionTriggerKind.Deletion; - } - private static bool TryCreateMatchResult( CompletionHelper completionHelper, VSCompletionItem item, @@ -607,122 +640,17 @@ private static bool TryCreateMatchResult( CompletionFilterReason filterReason, ImmutableArray recentItems, bool highlightMatchingPortions, - ref int currentIndex, - out MatchResult matchResult) + int currentIndex, + out MatchResult matchResult) { var roslynItem = GetOrAddRoslynCompletionItem(item); - - // Get the match of the given completion item for the pattern provided so far. - // A completion item is checked against the pattern by see if it's - // CompletionItem.FilterText matches the item. That way, the pattern it checked - // against terms like "IList" and not IList<>. - // Note that the check on filter text length is purely for efficiency, we should - // get the same result with or without it. - var patternMatch = filterText.Length > 0 - ? completionHelper.GetMatch(item.FilterText, filterText, includeMatchSpans: highlightMatchingPortions, CultureInfo.CurrentCulture) - : null; - - var matchedFilterText = MatchesFilterText( - roslynItem, - filterText, - initialTriggerKind, - filterReason, - recentItems, - patternMatch); - - if (matchedFilterText || KeepAllItemsInTheList(initialTriggerKind, filterText)) - { - matchResult = new MatchResult( - roslynItem, item, matchedFilterText: matchedFilterText, - patternMatch: patternMatch, index: currentIndex++, GetHighlightedSpans(completionHelper, item, filterText, highlightMatchingPortions, roslynItem, patternMatch)); - - return true; - } - - matchResult = default; - return false; - - static ImmutableArray GetHighlightedSpans( - CompletionHelper completionHelper, - VSCompletionItem item, - string filterText, - bool highlightMatchingPortions, - RoslynCompletionItem roslynItem, - PatternMatch? patternMatch) - { - if (!highlightMatchingPortions) - { - return ImmutableArray.Empty; - } - - if (roslynItem.HasDifferentFilterText) - { - // The PatternMatch in MatchResult is calculated based on Roslyn item's FilterText, - // which can be used to calculate highlighted span for VSCompletion item's DisplayText w/o doing the matching again. - // However, if the Roslyn item's FilterText is different from its DisplayText, - // we need to do the match against the display text of the VS item directly to get the highlighted spans. - return completionHelper.GetHighlightedSpans( - item.DisplayText, filterText, CultureInfo.CurrentCulture).SelectAsArray(s => s.ToSpan()); - } - - if (patternMatch.HasValue) - { - // Since VS item's display text is created as Prefix + DisplayText + Suffix, - // we can calculate the highlighted span by adding an offset that is the length of the Prefix. - return patternMatch.Value.MatchedSpans - .SelectAsArray(s_highlightSpanGetter, roslynItem); - } - - // If there's no match for Roslyn item's filter text which is identical to its display text, - // then we can safely assume there'd be no matching to VS item's display text. - return ImmutableArray.Empty; - } + return CompletionHelper.TryCreateMatchResult(completionHelper, roslynItem, item, filterText, initialTriggerKind, filterReason, recentItems, highlightMatchingPortions, currentIndex, out matchResult); } // PERF: Create a singleton to avoid lambda allocation on hot path private static readonly Func s_highlightSpanGetter = (span, item) => span.MoveTo(item.DisplayTextPrefix?.Length ?? 0).ToSpan(); - private static bool MatchesFilterText( - RoslynCompletionItem item, - string filterText, - CompletionTriggerKind initialTriggerKind, - CompletionFilterReason filterReason, - ImmutableArray recentItems, - PatternMatch? patternMatch) - { - // For the deletion we bake in the core logic for how matching should work. - // This way deletion feels the same across all languages that opt into deletion - // as a completion trigger. - - // Specifically, to avoid being too aggressive when matching an item during - // completion, we require that the current filter text be a prefix of the - // item in the list. - if (filterReason == CompletionFilterReason.Deletion && - initialTriggerKind == CompletionTriggerKind.Deletion) - { - return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; - } - - // If the user hasn't typed anything, and this item was preselected, or was in the - // MRU list, then we definitely want to include it. - if (filterText.Length == 0) - { - if (item.Rules.MatchPriority > MatchPriority.Default) - { - return true; - } - - if (!recentItems.IsDefault && GetRecentItemIndex(recentItems, item) <= 0) - { - return true; - } - } - - // Otherwise, the item matches filter text if a pattern match is returned. - return patternMatch != null; - } - private static bool IsHardSelection( string filterText, RoslynCompletionItem item, diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs index 629e21ae5fa55..6e9ceb2cf3624 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs @@ -22,20 +22,16 @@ internal static class Helpers { internal static IReadOnlyCollection BuildInteractiveTextElements( ImmutableArray taggedTexts, - Document document, - IThreadingContext threadingContext, - Lazy streamingPresenter) + IntellisenseQuickInfoBuilderContext? context) { var index = 0; - return BuildInteractiveTextElements(taggedTexts, ref index, document, threadingContext, streamingPresenter); + return BuildInteractiveTextElements(taggedTexts, ref index, context); } private static IReadOnlyCollection BuildInteractiveTextElements( ImmutableArray taggedTexts, ref int index, - Document document, - IThreadingContext? threadingContext, - Lazy? streamingPresenter) + IntellisenseQuickInfoBuilderContext? context) { // This method produces a sequence of zero or more paragraphs var paragraphs = new List(); @@ -67,7 +63,7 @@ private static IReadOnlyCollection BuildInteractiveTextElements( } index++; - var nestedElements = BuildInteractiveTextElements(taggedTexts, ref index, document, threadingContext, streamingPresenter); + var nestedElements = BuildInteractiveTextElements(taggedTexts, ref index, context); if (nestedElements.Count <= 1) { currentParagraph.Add(new ContainerElement( @@ -136,8 +132,11 @@ private static IReadOnlyCollection BuildInteractiveTextElements( { // This is tagged text getting added to the current line we are building. var style = GetClassifiedTextRunStyle(part.Style); - if (part.NavigationTarget is object && streamingPresenter != null && threadingContext != null) + if (part.NavigationTarget is object && + context?.ThreadingContext is ThreadingContext threadingContext && + context.StreamingPresenter is Lazy streamingPresenter) { + var document = context.Document; if (Uri.TryCreate(part.NavigationTarget, UriKind.Absolute, out var absoluteUri)) { var target = new QuickInfoHyperLink(document.Project.Solution.Workspace, absoluteUri); diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs index a313b77bd03c8..89a300097727c 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -27,9 +25,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo internal static class IntellisenseQuickInfoBuilder { private static async Task BuildInteractiveContentAsync(CodeAnalysisQuickInfoItem quickInfoItem, - Document document, - IThreadingContext threadingContext, - Lazy streamingPresenter, + IntellisenseQuickInfoBuilderContext? context, CancellationToken cancellationToken) { // Build the first line of QuickInfo item, the images and the Description section should be on the first line with Wrapped style @@ -52,7 +48,7 @@ private static async Task BuildInteractiveContentAsync(CodeAna if (descSection != null) { var isFirstElement = true; - foreach (var element in Helpers.BuildInteractiveTextElements(descSection.TaggedParts, document, threadingContext, streamingPresenter)) + foreach (var element in Helpers.BuildInteractiveTextElements(descSection.TaggedParts, context)) { if (isFirstElement) { @@ -74,7 +70,7 @@ private static async Task BuildInteractiveContentAsync(CodeAna if (documentationCommentSection != null) { var isFirstElement = true; - foreach (var element in Helpers.BuildInteractiveTextElements(documentationCommentSection.TaggedParts, document, threadingContext, streamingPresenter)) + foreach (var element in Helpers.BuildInteractiveTextElements(documentationCommentSection.TaggedParts, context)) { if (isFirstElement) { @@ -98,10 +94,10 @@ private static async Task BuildInteractiveContentAsync(CodeAna // Add the remaining sections as Stacked style elements.AddRange( quickInfoItem.Sections.Where(s => s.Kind != QuickInfoSectionKinds.Description && s.Kind != QuickInfoSectionKinds.DocumentationComments) - .SelectMany(s => Helpers.BuildInteractiveTextElements(s.TaggedParts, document, threadingContext, streamingPresenter))); + .SelectMany(s => Helpers.BuildInteractiveTextElements(s.TaggedParts, context))); // build text for RelatedSpan - if (quickInfoItem.RelatedSpans.Any()) + if (quickInfoItem.RelatedSpans.Any() && context?.Document is Document document) { var classifiedSpanList = new List(); foreach (var span in quickInfoItem.RelatedSpans) @@ -134,7 +130,8 @@ internal static async Task BuildItemAsync( Lazy streamingPresenter, CancellationToken cancellationToken) { - var content = await BuildInteractiveContentAsync(quickInfoItem, document, threadingContext, streamingPresenter, cancellationToken).ConfigureAwait(false); + var context = new IntellisenseQuickInfoBuilderContext(document, threadingContext, streamingPresenter); + var content = await BuildInteractiveContentAsync(quickInfoItem, context, cancellationToken).ConfigureAwait(false); return new IntellisenseQuickInfoItem(trackingSpan, content); } @@ -147,10 +144,10 @@ internal static async Task BuildItemAsync( /// internal static Task BuildContentWithoutNavigationActionsAsync( CodeAnalysisQuickInfoItem quickInfoItem, - Document document, + IntellisenseQuickInfoBuilderContext? context, CancellationToken cancellationToken) { - return BuildInteractiveContentAsync(quickInfoItem, document, threadingContext: null, streamingPresenter: null, cancellationToken); + return BuildInteractiveContentAsync(quickInfoItem, context, cancellationToken); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilderContext.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilderContext.cs new file mode 100644 index 0000000000000..ca453d49c4b66 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilderContext.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo +{ + /// + /// Context to build content for quick info item for intellisense. + /// + internal sealed class IntellisenseQuickInfoBuilderContext + { + public IntellisenseQuickInfoBuilderContext( + Document document, + IThreadingContext? threadingContext, + Lazy? streamingPresenter) + { + Document = document; + ThreadingContext = threadingContext; + StreamingPresenter = streamingPresenter; + } + + public Document Document { get; } + public IThreadingContext? ThreadingContext { get; } + public Lazy? StreamingPresenter { get; } + } +} diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index a645f439539ea..e43ae4c6a9b80 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -3,13 +3,16 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Implementation.Classification; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -28,7 +31,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar /// The threading model for this class is simple: all non-static members are affinitized to the /// UI thread. /// - internal partial class NavigationBarController : ForegroundThreadAffinitizedObject, INavigationBarController + internal partial class NavigationBarController : ForegroundThreadAffinitizedObject, IDisposable { private readonly INavigationBarPresenter _presenter; private readonly ITextBuffer _subjectBuffer; @@ -36,12 +39,11 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje private readonly IAsynchronousOperationListener _asyncListener; private bool _disconnected = false; - private Workspace? _workspace; /// - /// Latest model and selected items produced once completes and - /// presents the single item to the view. These can then be read in when the dropdown is expanded and we want - /// to show all items. + /// Latest model and selected items produced once completes and presents the + /// single item to the view. These can then be read in when the dropdown is expanded and we want to show all + /// items. /// private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _latestModelAndSelectedInfo_OnlyAccessOnUIThread; @@ -51,6 +53,26 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje /// private (ImmutableArray projectItems, NavigationBarProjectItem? selectedProjectItem, NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastPresentedInfo; + /// + /// Source of events that should cause us to update the nav bar model with new information. + /// + private readonly ITaggerEventSource _eventSource; + + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + /// + /// Queue to batch up work to do to compute the current model. Used so we can batch up a lot of events and only + /// compute the model once for every batch. The bool type parameter isn't used, but is provided as this + /// type is generic. + /// + private readonly AsyncBatchingWorkQueue _computeModelQueue; + + /// + /// Queue to batch up work to do to determine the selected item. Used so we can batch up a lot of events and + /// only compute the selected item once for every batch. + /// + private readonly AsyncBatchingWorkQueue _selectItemQueue; + public NavigationBarController( IThreadingContext threadingContext, INavigationBarPresenter presenter, @@ -64,86 +86,57 @@ public NavigationBarController( _uiThreadOperationExecutor = uiThreadOperationExecutor; _asyncListener = asyncListener; + _computeModelQueue = new AsyncBatchingWorkQueue( + TimeSpan.FromMilliseconds(TaggerConstants.ShortDelay), + ComputeModelAndSelectItemAsync, + EqualityComparer.Default, + asyncListener, + _cancellationTokenSource.Token); + + _selectItemQueue = new AsyncBatchingWorkQueue( + TimeSpan.FromMilliseconds(TaggerConstants.NearImmediateDelay), + SelectItemAsync, + asyncListener, + _cancellationTokenSource.Token); + presenter.CaretMoved += OnCaretMoved; presenter.ViewFocused += OnViewFocused; presenter.DropDownFocused += OnDropDownFocused; presenter.ItemSelected += OnItemSelected; - subjectBuffer.PostChanged += OnSubjectBufferPostChanged; - // Initialize the tasks to be an empty model so we never have to deal with a null case. - _latestModelAndSelectedInfo_OnlyAccessOnUIThread.model = new( - ImmutableArray.Empty, - semanticVersionStamp: default, - itemService: null!); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread.model = new(ImmutableArray.Empty, itemService: null!); _latestModelAndSelectedInfo_OnlyAccessOnUIThread.selectedInfo = new(typeItem: null, memberItem: null); - _modelTask = Task.FromResult(_latestModelAndSelectedInfo_OnlyAccessOnUIThread.model); - } - - public void SetWorkspace(Workspace? newWorkspace) - { - DisconnectFromWorkspace(); - - if (newWorkspace != null) - { - ConnectToWorkspace(newWorkspace); - } - } - - private void ConnectToWorkspace(Workspace workspace) - { - // If we disconnected before the workspace ever connected, just disregard - if (_disconnected) - { - return; - } - - _workspace = workspace; - _workspace.WorkspaceChanged += this.OnWorkspaceChanged; - _workspace.DocumentActiveContextChanged += this.OnDocumentActiveContextChanged; - - if (IsForeground()) - { - ConnectToNewWorkspace(); - } - else - { - var asyncToken = _asyncListener.BeginAsyncOperation(nameof(ConnectToWorkspace)); - Task.Run(async () => - { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - - ConnectToNewWorkspace(); - }).CompletesAsyncOperation(asyncToken); - } - - return; - - void ConnectToNewWorkspace() - { - // For the first time you open the file, we'll start immediately - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); - } + // Use 'compilation available' as that may produce different results from the initial 'frozen partial' + // snapshot we use. + _eventSource = new CompilationAvailableTaggerEventSource( + subjectBuffer, + asyncListener, + // Any time an edit happens, recompute as the nav bar items may have changed. + TaggerEventSources.OnTextChanged(subjectBuffer), + // Switching what is the active context may change the nav bar contents. + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer), + // Many workspace changes may need us to change the items (like options changing, or project renaming). + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, asyncListener), + // Once we hook this buffer up to the workspace, then we can start computing the nav bar items. + TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer)); + _eventSource.Changed += OnEventSourceChanged; + _eventSource.Connect(); + + // Kick off initial work to populate the navbars + StartModelUpdateAndSelectedItemUpdateTasks(); } - private void DisconnectFromWorkspace() + private void OnEventSourceChanged(object? sender, TaggerEventArgs e) { - if (_workspace != null) - { - _workspace.DocumentActiveContextChanged -= this.OnDocumentActiveContextChanged; - _workspace.WorkspaceChanged -= this.OnWorkspaceChanged; - _workspace = null; - } + StartModelUpdateAndSelectedItemUpdateTasks(); } - public void Disconnect() + void IDisposable.Dispose() { AssertIsForeground(); - DisconnectFromWorkspace(); - - _subjectBuffer.PostChanged -= OnSubjectBufferPostChanged; _presenter.CaretMoved -= OnCaretMoved; _presenter.ViewFocused -= OnViewFocused; @@ -153,81 +146,35 @@ public void Disconnect() _presenter.Disconnect(); + _eventSource.Changed -= OnEventSourceChanged; + _eventSource.Disconnect(); + _disconnected = true; // Cancel off any remaining background work - _modelTaskCancellationSource.Cancel(); - _selectedItemInfoTaskCancellationSource.Cancel(); + _cancellationTokenSource.Cancel(); } - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) + private void StartModelUpdateAndSelectedItemUpdateTasks() { - // We're getting an event for a workspace we already disconnected from - if (args.NewSolution.Workspace != _workspace) - { - return; - } - - // If the displayed project is being renamed, retrigger the update - if (args.Kind == WorkspaceChangeKind.ProjectChanged && args.ProjectId != null) - { - var oldProject = args.OldSolution.GetRequiredProject(args.ProjectId); - var newProject = args.NewSolution.GetRequiredProject(args.ProjectId); - - if (oldProject.Name != newProject.Name) - { - var currentContextDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - - if (currentContextDocumentId != null && currentContextDocumentId.ProjectId == args.ProjectId) - { - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); - } - } - } - - if (args.Kind == WorkspaceChangeKind.DocumentChanged && - args.OldSolution == args.NewSolution) - { - var currentContextDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - if (currentContextDocumentId != null && currentContextDocumentId == args.DocumentId) - { - // The context has changed, so update everything. - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); - } - } - } - - private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs args) - { - AssertIsForeground(); - if (args.Solution.Workspace != _workspace) + // If we disconnected already, just disregard + if (_disconnected) return; - var currentContextDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - if (args.NewActiveContextDocumentId == currentContextDocumentId || - args.OldActiveContextDocumentId == currentContextDocumentId) - { - // if the active context changed, recompute the types/member as they may be changed as well. - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); - } - } - - private void OnSubjectBufferPostChanged(object? sender, EventArgs e) - { - AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay); + // 'true' value is unused. this just signals to the queue that we have work to do. + _computeModelQueue.AddWork(true); } private void OnCaretMoved(object? sender, EventArgs e) { AssertIsForeground(); - StartSelectedItemUpdateTask(delay: TaggerConstants.NearImmediateDelay); + StartSelectedItemUpdateTask(); } private void OnViewFocused(object? sender, EventArgs e) { AssertIsForeground(); - StartSelectedItemUpdateTask(delay: TaggerConstants.ShortDelay); + StartSelectedItemUpdateTask(); } private void OnDropDownFocused(object? sender, EventArgs e) @@ -393,9 +340,7 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio } // Now that the edit has been done, refresh to make sure everything is up-to-date. - // Have to make sure we come back to the main thread for this. - AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(); } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs index 0c4460514dd88..5f7166993c57f 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs @@ -34,7 +34,7 @@ public NavigationBarControllerFactoryService( _asyncListener = listenerProvider.GetListener(FeatureAttribute.NavigationBar); } - public INavigationBarController CreateController(INavigationBarPresenter presenter, ITextBuffer textBuffer) + public IDisposable CreateController(INavigationBarPresenter presenter, ITextBuffer textBuffer) { return new NavigationBarController( _threadingContext, diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index cc6ddd26ee71f..2e3f26f6f2fcd 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -4,17 +4,16 @@ #nullable disable -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Threading; @@ -23,155 +22,89 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { internal partial class NavigationBarController { - /// - /// The computation of the last model. - /// - private Task _modelTask; - - private CancellationTokenSource _modelTaskCancellationSource = new(); - private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); - /// /// Starts a new task to compute the model based on the current text. /// - private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay) + private async ValueTask ComputeModelAndSelectItemAsync(ImmutableArray unused, CancellationToken cancellationToken) { - AssertIsForeground(); - + // Jump back to the UI thread to determine what snapshot the user is processing. + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var textSnapshot = _subjectBuffer.CurrentSnapshot; - // Cancel off any existing work - _modelTaskCancellationSource.Cancel(); - - _modelTaskCancellationSource = new CancellationTokenSource(); - var cancellationToken = _modelTaskCancellationSource.Token; - - // Enqueue a new computation for the model - var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartModelUpdateTask"); - _modelTask = ComputeModelAfterDelayAsync(_modelTask, textSnapshot, modelUpdateDelay, cancellationToken); - _modelTask.CompletesAsyncOperation(asyncToken); - - StartSelectedItemUpdateTask(delay: 0); - } - - private static async Task ComputeModelAfterDelayAsync( - Task modelTask, ITextSnapshot textSnapshot, int modelUpdateDelay, CancellationToken cancellationToken) - { - var previousModel = await modelTask.ConfigureAwait(false); - if (!cancellationToken.IsCancellationRequested) - { - try - { - await Task.Delay(modelUpdateDelay, cancellationToken).ConfigureAwait(false); - return await ComputeModelAsync(previousModel, textSnapshot, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - } - } - - // If we canceled, then just return along whatever we have computed so far. Note: this means the - // _modelTask task will never enter the canceled state. It always represents the last successfully - // computed model. - return previousModel; - } - - /// - /// Computes a model for the given snapshot. - /// - private static async Task ComputeModelAsync( - NavigationBarModel lastCompletedModel, ITextSnapshot snapshot, CancellationToken cancellationToken) - { // Ensure we switch to the threadpool before calling GetDocumentWithFrozenPartialSemantics. It ensures // that any IO that performs is not potentially on the UI thread. await TaskScheduler.Default; - // When computing items just get the partial semantics workspace. This will ensure we can get data for this - // file, and hopefully have enough loaded to get data for other files in the case of partial types. In the - // event the other files aren't available, then partial-type information won't be correct. That's ok though - // as this is just something that happens during solution load and will pass once that is over. By using - // partial semantics, we can ensure we don't spend an inordinate amount of time computing and using full - // compilation data (like skeleton assemblies). - var document = snapshot.AsText().GetDocumentWithFrozenPartialSemantics(cancellationToken); - if (document == null) - return null; + var model = await ComputeModelAsync(textSnapshot, cancellationToken).ConfigureAwait(false); + + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + // Now, enqueue work to select the right item in this new model. + StartSelectedItemUpdateTask(); + + return model; - var languageService = document.GetLanguageService(); - if (languageService != null) + static async Task ComputeModelAsync(ITextSnapshot textSnapshot, CancellationToken cancellationToken) { - // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. - // the model should be only updated here - if (lastCompletedModel != null) + // When computing items just get the partial semantics workspace. This will ensure we can get data for this + // file, and hopefully have enough loaded to get data for other files in the case of partial types. In the + // event the other files aren't available, then partial-type information won't be correct. That's ok though + // as this is just something that happens during solution load and will pass once that is over. By using + // partial semantics, we can ensure we don't spend an inordinate amount of time computing and using full + // compilation data (like skeleton assemblies). + var document = textSnapshot.AsText().GetDocumentWithFrozenPartialSemantics(cancellationToken); + if (document == null) + return null; + + var itemService = document.GetLanguageService(); + if (itemService != null) { - var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); - if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) + using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) { - // it looks like we can re-use previous model - return lastCompletedModel; + var items = await itemService.GetItemsAsync(document, textSnapshot, cancellationToken).ConfigureAwait(false); + return new NavigationBarModel(items, itemService); } } - using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) - { - var items = await languageService.GetItemsAsync(document, snapshot, cancellationToken).ConfigureAwait(false); - var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - return new NavigationBarModel(items, version, languageService); - } + return new NavigationBarModel(ImmutableArray.Empty, itemService: null); } - - return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } /// /// Starts a new task to compute what item should be selected. /// - private void StartSelectedItemUpdateTask(int delay) + private void StartSelectedItemUpdateTask() { - AssertIsForeground(); - - var currentView = _presenter.TryGetCurrentView(); - var subjectBufferCaretPosition = currentView?.GetCaretPoint(_subjectBuffer); - if (!subjectBufferCaretPosition.HasValue) - return; - - // Cancel off any existing work - _selectedItemInfoTaskCancellationSource.Cancel(); - _selectedItemInfoTaskCancellationSource = new CancellationTokenSource(); - var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; - - var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); - var selectedItemInfoTask = DetermineSelectedItemInfoAsync(_modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); - selectedItemInfoTask.CompletesAsyncOperation(asyncToken); + // 'true' value is unused. this just signals to the queue that we have work to do. + _selectItemQueue.AddWork(); } - private async Task DetermineSelectedItemInfoAsync( - Task lastModelTask, - int delay, - SnapshotPoint caretPosition, - CancellationToken cancellationToken) + private async ValueTask SelectItemAsync(CancellationToken cancellationToken) { - // First wait the delay before doing any other work. That way if we get canceled due to other events (like - // the user moving around), we don't end up doing anything, and the next task can take over. - await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + // Switch to the UI so we can determine where the user is. + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var lastModel = await lastModelTask.ConfigureAwait(false); - if (cancellationToken.IsCancellationRequested) + var currentView = _presenter.TryGetCurrentView(); + var caretPosition = currentView?.GetCaretPoint(_subjectBuffer); + if (!caretPosition.HasValue) return; - var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); + // Ensure the latest model is computed and grab it while on the UI thread. + var model = await _computeModelQueue.WaitUntilCurrentBatchCompletesAsync().ConfigureAwait(true); - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + // Jump back to the BG to do any expensive work walking the entire model + await TaskScheduler.Default; + + var currentSelectedItem = ComputeSelectedTypeAndMember(model, caretPosition.Value, cancellationToken); - AssertIsForeground(); + // Finally, switch back to the UI to update our state and UI. + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // Update the UI to show *just* the type/member that was selected. We don't need it to know about all items // as the user can only see one at a time as they're editing in a document. However, once we've done this, // store the full list of items as well so that if the user expands the dropdown, we can take all those // values and shove them in so it appears as if the lists were always fully realized. - _latestModelAndSelectedInfo_OnlyAccessOnUIThread = (lastModel, currentSelectedItem); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread = (model, currentSelectedItem); PushSelectedItemsToPresenter(currentSelectedItem); } @@ -247,55 +180,5 @@ private static (T item, bool gray) GetMatchingItem(IEnumerable items, Snap return (itemToGray, gray: itemToGray != null); } } - - private static bool SpanStillValid(NavigationBarModel model, ITextSnapshot snapshot, CancellationToken cancellationToken) - { - // even if semantic version is same, portion of text could have been copied & pasted with - // exact same top level content. - // go through spans to see whether this happened. - // - // paying cost of moving spans forward shouldn't be matter since we need to pay that - // price soon or later to figure out selected item. - foreach (var type in model.Types) - { - if (!SpansStillValid(type, snapshot)) - return false; - - foreach (var member in type.ChildItems) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!SpansStillValid(member, snapshot)) - return false; - } - } - - return true; - } - - private static bool SpansStillValid(NavigationBarItem item, ITextSnapshot snapshot) - { - if (item.NavigationTrackingSpan != null) - { - var currentSpan = item.NavigationTrackingSpan.GetSpan(snapshot); - if (currentSpan.IsEmpty) - return false; - } - - foreach (var span in item.TrackingSpans) - { - var currentSpan = span.GetSpan(snapshot); - if (currentSpan.IsEmpty) - return false; - } - - foreach (var childItem in item.ChildItems) - { - if (!SpansStillValid(childItem, snapshot)) - return false; - } - - return true; - } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs index 99faf5fad32cd..71173991e818d 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs @@ -3,27 +3,17 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { internal sealed class NavigationBarModel { public ImmutableArray Types { get; } - - /// - /// The VersionStamp of the project when this model was computed. - /// - public VersionStamp SemanticVersionStamp { get; } - public INavigationBarItemService ItemService { get; } - public NavigationBarModel(ImmutableArray types, VersionStamp semanticVersionStamp, INavigationBarItemService itemService) + public NavigationBarModel(ImmutableArray types, INavigationBarItemService itemService) { - Contract.ThrowIfNull(types); - this.Types = types; - this.SemanticVersionStamp = semanticVersionStamp; this.ItemService = itemService; } } diff --git a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs index a7c730d93ee17..0f0fc8d096f81 100644 --- a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs @@ -202,6 +202,7 @@ private static ImmutableArray GetMultiLineRegions( { } } + continue; } diff --git a/src/EditorFeatures/Core/Implementation/Structure/StructureTag.cs b/src/EditorFeatures/Core/Implementation/Structure/StructureTag.cs index 75504548dbd4a..527fc7d0c5fe4 100644 --- a/src/EditorFeatures/Core/Implementation/Structure/StructureTag.cs +++ b/src/EditorFeatures/Core/Implementation/Structure/StructureTag.cs @@ -80,6 +80,7 @@ private static string ConvertType(string type) BlockTypes.Loop => PredefinedStructureTagTypes.Loop, BlockTypes.Member => PredefinedStructureTagTypes.Member, BlockTypes.Namespace => PredefinedStructureTagTypes.Namespace, + BlockTypes.Nonstructural => PredefinedStructureTagTypes.Nonstructural, BlockTypes.PreprocessorRegion => PredefinedStructureTagTypes.PreprocessorRegion, BlockTypes.Statement => PredefinedStructureTagTypes.Statement, BlockTypes.Type => PredefinedStructureTagTypes.Type, diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs index aeff2305ff6a4..dfce03777c761 100644 --- a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs +++ b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces [Shared] internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory { - private const int ImplicitCacheTimeoutInMS = 10000; + private static readonly TimeSpan s_implicitCacheTimeout = TimeSpan.FromMilliseconds(10000); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -30,7 +30,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) return new ProjectCacheService(workspaceServices.Workspace); } - var service = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + var service = new ProjectCacheService(workspaceServices.Workspace, s_implicitCacheTimeout); // Also clear the cache when the solution is cleared or removed. workspaceServices.Workspace.WorkspaceChanged += (s, e) => diff --git a/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs b/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs index b48d1535a71f8..19ccfd66c8666 100644 --- a/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs +++ b/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs @@ -87,13 +87,16 @@ public async ValueTask> GetInheritanceMemb private static bool CanHaveInheritanceTarget(ISymbol symbol) { - if (symbol.IsStatic) + if (symbol is INamedTypeSymbol namedType) { - return false; + return !symbol.IsStatic && namedType.TypeKind is TypeKind.Interface or TypeKind.Class or TypeKind.Struct; } - if (symbol is INamedTypeSymbol or IEventSymbol or IPropertySymbol - or IMethodSymbol { MethodKind: MethodKind.Ordinary or MethodKind.ExplicitInterfaceImplementation }) + if (symbol is IEventSymbol or IPropertySymbol + or IMethodSymbol + { + MethodKind: MethodKind.Ordinary or MethodKind.ExplicitInterfaceImplementation or MethodKind.UserDefinedOperator or MethodKind.Conversion + }) { return true; } diff --git a/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs b/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs index 02279bca1ab74..875b49ff4f9bb 100644 --- a/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs +++ b/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -76,6 +77,7 @@ private static async ValueTask foreach (var (symbolKey, lineNumber) in symbolKeyAndLineNumbers) { var symbol = symbolKey.Resolve(compilation, cancellationToken: cancellationToken).Symbol; + if (symbol is INamedTypeSymbol namedTypeSymbol) { await AddInheritanceMemberItemsForNamedTypeAsync(solution, namedTypeSymbol, lineNumber, builder, cancellationToken).ConfigureAwait(false); @@ -83,7 +85,7 @@ private static async ValueTask if (symbol is IEventSymbol or IPropertySymbol or IMethodSymbol) { - await AddInheritanceMemberItemsForTypeMembersAsync(solution, symbol, lineNumber, builder, cancellationToken).ConfigureAwait(false); + await AddInheritanceMemberItemsForMembersAsync(solution, symbol, lineNumber, builder, cancellationToken).ConfigureAwait(false); } } @@ -124,68 +126,92 @@ private static async ValueTask AddInheritanceMemberItemsForNamedTypeAsync( if (baseSymbols.Any() || derivedSymbols.Any()) { - var item = await CreateInheritanceMemberItemAsync( - solution, - memberSymbol, - lineNumber, - baseSymbols: baseSymbols.CastArray(), - derivedTypesSymbols: derivedSymbols.CastArray(), - cancellationToken).ConfigureAwait(false); - builder.AddIfNotNull(item); + if (memberSymbol.TypeKind == TypeKind.Interface) + { + var item = await CreateInheritanceMemberItemForInterfaceAsync( + solution, + memberSymbol, + lineNumber, + baseSymbols: baseSymbols.CastArray(), + derivedTypesSymbols: derivedSymbols.CastArray(), + cancellationToken).ConfigureAwait(false); + builder.AddIfNotNull(item); + } + else + { + Debug.Assert(memberSymbol.TypeKind is TypeKind.Class or TypeKind.Struct); + var item = await CreateInheritanceItemForClassAndStructureAsync( + solution, + memberSymbol, + lineNumber, + baseSymbols: baseSymbols.CastArray(), + derivedTypesSymbols: derivedSymbols.CastArray(), + cancellationToken).ConfigureAwait(false); + builder.AddIfNotNull(item); + } } } - private static async ValueTask AddInheritanceMemberItemsForTypeMembersAsync( + private static async ValueTask AddInheritanceMemberItemsForMembersAsync( Solution solution, ISymbol memberSymbol, int lineNumber, ArrayBuilder builder, CancellationToken cancellationToken) { - // For a given member symbol (method, property and event), its base and derived symbols are classified into 4 cases. - // The mapping between images - // Implemented : I↓ - // Implementing : I↑ - // Overridden: O↓ - // Overriding: O↑ - - // Go down the inheritance chain to find all the overrides targets. - var allOverriddenSymbols = await SymbolFinder.FindOverridesArrayAsync(memberSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + if (memberSymbol.ContainingSymbol.IsInterfaceType()) + { + // Go down the inheritance chain to find all the implementing targets. + var allImplementingSymbols = await GetImplementingSymbolsForTypeMemberAsync(solution, memberSymbol, cancellationToken).ConfigureAwait(false); - // Go up the inheritance chain to find all overriding targets - var overridingSymbols = GetOverridingSymbols(memberSymbol); + // For all implementing symbols, make sure it is in source. + // For example, if the user is viewing IEnumerable from metadata, + // then don't show the derived overriden & implemented types in System.Collections + var implementingSymbols = allImplementingSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); - // Go up the inheritance chain to find all the implemented targets. - var implementingSymbols = GetImplementingSymbolsForTypeMember(memberSymbol, overridingSymbols); + if (implementingSymbols.Any()) + { + var item = await CreateInheritanceMemberItemForInterfaceMemberAsync(solution, + memberSymbol, + lineNumber, + implementingMembers: implementingSymbols, + cancellationToken).ConfigureAwait(false); + builder.AddIfNotNull(item); + } + } + else + { + // Go down the inheritance chain to find all the overriding targets. + var allOverridingSymbols = await SymbolFinder.FindOverridesArrayAsync(memberSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); - // Go down the inheritance chain to find all the implementing targets. - var allImplementedSymbols = await GetImplementedSymbolsForTypeMemberAsync(solution, memberSymbol, cancellationToken).ConfigureAwait(false); + // Go up the inheritance chain to find all overridden targets + var overriddenSymbols = GetOverriddenSymbols(memberSymbol); - // For all overriden & implemented symbols, make sure it is in source. - // For example, if the user is viewing System.Threading.SynchronizationContext from metadata, - // then don't show the derived overriden & implemented method in the default implementation for System.Threading.SynchronizationContext in metadata - var overriddenSymbols = allOverriddenSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); - var implementedSymbols = allImplementedSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); + // Go up the inheritance chain to find all the implemented targets. + var implementedSymbols = GetImplementedSymbolsForTypeMember(memberSymbol, overriddenSymbols); - if (overriddenSymbols.Any() || overridingSymbols.Any() || implementingSymbols.Any() || implementedSymbols.Any()) - { - var item = await CreateInheritanceMemberInfoForMemberAsync( - solution, - memberSymbol, - lineNumber, - implementingMembers: implementingSymbols, - implementedMembers: implementedSymbols, - overridenMembers: overriddenSymbols, - overridingMembers: overridingSymbols, - cancellationToken).ConfigureAwait(false); + // For all overriding symbols, make sure it is in source. + // For example, if the user is viewing System.Threading.SynchronizationContext from metadata, + // then don't show the derived overriden & implemented method in the default implementation for System.Threading.SynchronizationContext in metadata + var overridingSymbols = allOverridingSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); - builder.AddIfNotNull(item); + if (overridingSymbols.Any() || overriddenSymbols.Any() || implementedSymbols.Any()) + { + var item = await CreateInheritanceMemberItemForClassOrStructMemberAsync(solution, + memberSymbol, + lineNumber, + implementedMembers: implementedSymbols, + overridingMembers: overridingSymbols, + overriddenMembers: overriddenSymbols, + cancellationToken).ConfigureAwait(false); + builder.AddIfNotNull(item); + } } } - private static async ValueTask CreateInheritanceMemberItemAsync( + private static async ValueTask CreateInheritanceMemberItemForInterfaceAsync( Solution solution, - INamedTypeSymbol memberSymbol, + INamedTypeSymbol interfaceSymbol, int lineNumber, ImmutableArray baseSymbols, ImmutableArray derivedTypesSymbols, @@ -194,89 +220,158 @@ private static async ValueTask CreateInherita var baseSymbolItems = await baseSymbols .SelectAsArray(symbol => symbol.OriginalDefinition) .Distinct() - .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implementing, cancellationToken), cancellationToken) + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync( + solution, + symbol, + InheritanceRelationship.InheritedInterface, + cancellationToken), cancellationToken) .ConfigureAwait(false); var derivedTypeItems = await derivedTypesSymbols .SelectAsArray(symbol => symbol.OriginalDefinition) .Distinct() - .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implemented, cancellationToken), cancellationToken) + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, + symbol, + InheritanceRelationship.ImplementingType, + cancellationToken), cancellationToken) .ConfigureAwait(false); return new SerializableInheritanceMarginItem( lineNumber, - FindUsagesHelpers.GetDisplayParts(memberSymbol), - memberSymbol.GetGlyph(), + FindUsagesHelpers.GetDisplayParts(interfaceSymbol), + interfaceSymbol.GetGlyph(), baseSymbolItems.Concat(derivedTypeItems)); } - private static async ValueTask CreateInheritanceItemAsync( + private static async ValueTask CreateInheritanceMemberItemForInterfaceMemberAsync( Solution solution, - ISymbol targetSymbol, - InheritanceRelationship inheritanceRelationship, + ISymbol memberSymbol, + int lineNumber, + ImmutableArray implementingMembers, CancellationToken cancellationToken) { - var symbolInSource = await SymbolFinder.FindSourceDefinitionAsync(targetSymbol, solution, cancellationToken).ConfigureAwait(false); - targetSymbol = symbolInSource ?? targetSymbol; + var implementedMemberItems = await implementingMembers + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync( + solution, + symbol, + InheritanceRelationship.ImplementingMember, + cancellationToken), cancellationToken).ConfigureAwait(false); - // Right now the targets are not shown in a classified way. - var definition = await targetSymbol.ToNonClassifiedDefinitionItemAsync( - solution, - includeHiddenLocations: false, - cancellationToken: cancellationToken).ConfigureAwait(false); + return new SerializableInheritanceMarginItem( + lineNumber, + FindUsagesHelpers.GetDisplayParts(memberSymbol), + memberSymbol.GetGlyph(), + implementedMemberItems); + } - var displayName = targetSymbol.ToDisplayString(s_displayFormat); + private static async ValueTask CreateInheritanceItemForClassAndStructureAsync( + Solution solution, + INamedTypeSymbol memberSymbol, + int lineNumber, + ImmutableArray baseSymbols, + ImmutableArray derivedTypesSymbols, + CancellationToken cancellationToken) + { + // If the target is an interface, it would be shown as 'Inherited interface', + // and if it is an class/struct, it whould be shown as 'Base Type' + var baseSymbolItems = await baseSymbols + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync( + solution, + symbol, + symbol.IsInterfaceType() ? InheritanceRelationship.ImplementedInterface : InheritanceRelationship.BaseType, + cancellationToken), cancellationToken).ConfigureAwait(false); - return new SerializableInheritanceTargetItem( - inheritanceRelationship, - // Id is used by FAR service for caching, it is not used in inheritance margin - SerializableDefinitionItem.Dehydrate(id: 0, definition), - targetSymbol.GetGlyph(), - displayName); + var derivedTypeItems = await derivedTypesSymbols + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, + symbol, + InheritanceRelationship.DerivedType, + cancellationToken), cancellationToken) + .ConfigureAwait(false); + + return new SerializableInheritanceMarginItem( + lineNumber, + FindUsagesHelpers.GetDisplayParts(memberSymbol), + memberSymbol.GetGlyph(), + baseSymbolItems.Concat(derivedTypeItems)); } - private static async ValueTask CreateInheritanceMemberInfoForMemberAsync( + private static async ValueTask CreateInheritanceMemberItemForClassOrStructMemberAsync( Solution solution, ISymbol memberSymbol, int lineNumber, - ImmutableArray implementingMembers, ImmutableArray implementedMembers, - ImmutableArray overridenMembers, ImmutableArray overridingMembers, + ImmutableArray overriddenMembers, CancellationToken cancellationToken) { - var implementingMemberItems = await implementingMembers - .SelectAsArray(symbol => symbol.OriginalDefinition) - .Distinct() - .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implementing, cancellationToken), cancellationToken).ConfigureAwait(false); - var implementedMemberItems = await implementedMembers .SelectAsArray(symbol => symbol.OriginalDefinition) .Distinct() - .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implemented, cancellationToken), cancellationToken).ConfigureAwait(false); + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync( + solution, + symbol, + InheritanceRelationship.ImplementedMember, + cancellationToken), cancellationToken).ConfigureAwait(false); - var overridenMemberItems = await overridenMembers + var overridenMemberItems = await overriddenMembers .SelectAsArray(symbol => symbol.OriginalDefinition) .Distinct() - .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Overridden, cancellationToken), cancellationToken).ConfigureAwait(false); + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync( + solution, + symbol, + InheritanceRelationship.OverriddenMember, + cancellationToken), cancellationToken).ConfigureAwait(false); var overridingMemberItems = await overridingMembers .SelectAsArray(symbol => symbol.OriginalDefinition) .Distinct() - .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Overriding, cancellationToken), cancellationToken).ConfigureAwait(false); + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync( + solution, + symbol, + InheritanceRelationship.OverridingMember, + cancellationToken), cancellationToken).ConfigureAwait(false); return new SerializableInheritanceMarginItem( lineNumber, FindUsagesHelpers.GetDisplayParts(memberSymbol), memberSymbol.GetGlyph(), - implementingMemberItems.Concat(implementedMemberItems) - .Concat(overridenMemberItems) - .Concat(overridingMemberItems)); + implementedMemberItems.Concat(overridenMemberItems).Concat(overridingMemberItems)); + } + + private static async ValueTask CreateInheritanceItemAsync( + Solution solution, + ISymbol targetSymbol, + InheritanceRelationship inheritanceRelationship, + CancellationToken cancellationToken) + { + var symbolInSource = await SymbolFinder.FindSourceDefinitionAsync(targetSymbol, solution, cancellationToken).ConfigureAwait(false); + targetSymbol = symbolInSource ?? targetSymbol; + + // Right now the targets are not shown in a classified way. + var definition = await targetSymbol.ToNonClassifiedDefinitionItemAsync( + solution, + includeHiddenLocations: false, + cancellationToken: cancellationToken).ConfigureAwait(false); + + var displayName = targetSymbol.ToDisplayString(s_displayFormat); + + return new SerializableInheritanceTargetItem( + inheritanceRelationship, + // Id is used by FAR service for caching, it is not used in inheritance margin + SerializableDefinitionItem.Dehydrate(id: 0, definition), + targetSymbol.GetGlyph(), + displayName); } - private static ImmutableArray GetImplementingSymbolsForTypeMember( + private static ImmutableArray GetImplementedSymbolsForTypeMember( ISymbol memberSymbol, - ImmutableArray overridingSymbols) + ImmutableArray overriddenSymbols) { if (memberSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol) { @@ -286,13 +381,13 @@ private static ImmutableArray GetImplementingSymbolsForTypeMember( var directImplementingSymbols = memberSymbol.ExplicitOrImplicitInterfaceImplementations(); builder.AddRange(directImplementingSymbols); - // 2. Also add the direct implementing symbols for the overriding symbols. + // 2. Also add the direct implementing symbols for the overridden symbols. // For example: // interface IBar { void Foo(); } // class Bar : IBar { public override void Foo() { } } // class Bar2 : Bar { public override void Foo() { } } // For 'Bar2.Foo()', we need to find 'IBar.Foo()' - foreach (var symbol in overridingSymbols) + foreach (var symbol in overriddenSymbols) { builder.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); } @@ -304,20 +399,15 @@ private static ImmutableArray GetImplementingSymbolsForTypeMember( } /// - /// For the , get all the implemented symbols. - /// Table for the mapping between images and inheritanceRelationship - /// Implemented : I↓ - /// Implementing : I↑ - /// Overridden: O↓ - /// Overriding: O↑ + /// For the , get all the implementing symbols. /// - private static async Task> GetImplementedSymbolsForTypeMemberAsync( + private static async Task> GetImplementingSymbolsForTypeMemberAsync( Solution solution, ISymbol memberSymbol, CancellationToken cancellationToken) { if (memberSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol - && memberSymbol.ContainingSymbol.IsInterfaceType()) + && memberSymbol.ContainingSymbol.IsInterfaceType()) { using var _ = ArrayBuilder.GetInstance(out var builder); // 1. Find all direct implementations for this member @@ -344,14 +434,9 @@ private static async Task> GetImplementedSymbolsForTypeM } /// - /// Get members overriding the - /// Table for the mapping between images and inheritanceRelationship - /// Implemented : I↓ - /// Implementing : I↑ - /// Overridden: O↓ - /// Overriding: O↑ + /// Get overridden members the . /// - private static ImmutableArray GetOverridingSymbols(ISymbol memberSymbol) + private static ImmutableArray GetOverriddenSymbols(ISymbol memberSymbol) { if (memberSymbol is INamedTypeSymbol) { @@ -360,11 +445,11 @@ private static ImmutableArray GetOverridingSymbols(ISymbol memberSymbol else { using var _ = ArrayBuilder.GetInstance(out var builder); - for (var overridenMember = memberSymbol.GetOverriddenMember(); - overridenMember != null; - overridenMember = overridenMember.GetOverriddenMember()) + for (var overriddenMember = memberSymbol.GetOverriddenMember(); + overriddenMember != null; + overriddenMember = overriddenMember.GetOverriddenMember()) { - builder.Add(overridenMember.OriginalDefinition); + builder.Add(overriddenMember.OriginalDefinition); } return builder.ToImmutableArray(); diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index d75f775bae394..134ee1de0e3d7 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -7,7 +7,7 @@ netcoreapp3.1;netstandard2.0 true $(DefineConstants);EDITOR_FEATURES - partial + partial Microsoft.CodeAnalysis.EditorFeatures.Common diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/NagivateToHighlightReferenceCommandHandler.cs b/src/EditorFeatures/Core/ReferenceHighlighting/NavigateToHighlightReferenceCommandHandler.cs similarity index 100% rename from src/EditorFeatures/Core/ReferenceHighlighting/NagivateToHighlightReferenceCommandHandler.cs rename to src/EditorFeatures/Core/ReferenceHighlighting/NavigateToHighlightReferenceCommandHandler.cs diff --git a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs index beaeec8532aae..039aa8727007e 100644 --- a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs +++ b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs @@ -78,16 +78,19 @@ internal static class FeatureOnOffOptions nameof(FeatureOnOffOptions), nameof(OfferRemoveUnusedReferences), defaultValue: true, storageLocations: new RoamingProfileStorageLocation($"TextEditor.{nameof(OfferRemoveUnusedReferences)}")); - public static readonly PerLanguageOption2 ShowInheritanceMargin = + public static readonly PerLanguageOption2 ShowInheritanceMargin = new(nameof(FeatureOnOffOptions), nameof(ShowInheritanceMargin), - defaultValue: false, + defaultValue: null, new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.ShowInheritanceMargin")); public static readonly Option2 AutomaticallyCompleteStatementOnSemicolon = new( nameof(FeatureOnOffOptions), nameof(AutomaticallyCompleteStatementOnSemicolon), defaultValue: true, storageLocations: new RoamingProfileStorageLocation($"TextEditor.{nameof(AutomaticallyCompleteStatementOnSemicolon)}")); + public static readonly Option2 SkipAnalyzersForImplicitlyTriggeredBuilds = new( + nameof(FeatureOnOffOptions), nameof(SkipAnalyzersForImplicitlyTriggeredBuilds), defaultValue: true, + storageLocations: new RoamingProfileStorageLocation($"TextEditor.{nameof(SkipAnalyzersForImplicitlyTriggeredBuilds)}")); } [ExportOptionProvider, Shared] @@ -117,6 +120,7 @@ public FeatureOnOffOptionsProvider() FeatureOnOffOptions.AddImportsOnPaste, FeatureOnOffOptions.OfferRemoveUnusedReferences, FeatureOnOffOptions.ShowInheritanceMargin, - FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon); + FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon, + FeatureOnOffOptions.SkipAnalyzersForImplicitlyTriggeredBuilds); } } diff --git a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs index 0b5cf858ab7e5..ec124733a96ec 100644 --- a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs +++ b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs @@ -72,7 +72,7 @@ public void Register(Workspace workspace) private async Task AnalyzeAsync() { - var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); + var workerBackOffTimeSpan = InternalSolutionCrawlerOptions.PreviewBackOffTimeSpan; var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); var solution = _workspace.CurrentSolution; @@ -89,7 +89,7 @@ private async Task AnalyzeAsync() } // delay analyzing - await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false); + await _owner._listener.Delay(workerBackOffTimeSpan, _source.Token).ConfigureAwait(false); // do actual analysis if (textDocument is Document document) diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs index 63dca1866fc32..e00a949d931f0 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs @@ -15,7 +15,7 @@ internal partial class TaggerEventSources { private class WorkspaceChangedEventSource : AbstractWorkspaceTrackingTaggerEventSource { - private readonly AsyncBatchingDelay _asyncDelay; + private readonly AsyncBatchingWorkQueue _asyncDelay; public WorkspaceChangedEventSource( ITextBuffer subjectBuffer, @@ -24,12 +24,12 @@ public WorkspaceChangedEventSource( { // That will ensure that even if we get a flurry of workspace events that we // only process a tag change once. - _asyncDelay = new AsyncBatchingDelay( + _asyncDelay = new AsyncBatchingWorkQueue( TimeSpan.FromMilliseconds(250), - processAsync: cancellationToken => + processBatchAsync: cancellationToken => { RaiseChanged(); - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; }, asyncListener, CancellationToken.None); @@ -48,7 +48,7 @@ protected override void DisconnectFromWorkspace(Workspace workspace) } private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs eventArgs) - => _asyncDelay.RequeueWork(); + => _asyncDelay.AddWork(); } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs index c13b91768cc9a..1327e55cd7b6e 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs @@ -62,6 +62,7 @@ public CancellationToken GetCancellationToken(bool initialTags) public void EnqueueWork( Func workAsync, TaggerDelay delay, + IExpeditableDelaySource delaySource, IAsyncToken asyncToken, CancellationToken cancellationToken) { @@ -70,7 +71,8 @@ public void EnqueueWork( _eventWorkQueue = _eventWorkQueue.ContinueWithAfterDelayFromAsync( _ => workAsync(), cancellationToken, - (int)delay.ComputeTimeDelay().TotalMilliseconds, + delay.ComputeTimeDelay(), + delaySource, TaskContinuationOptions.None, TaskScheduler.Default).CompletesAsyncOperation(asyncToken); } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index b871b598e8846..7381dad55cd5c 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -188,7 +188,7 @@ private void EnqueueWork(bool initialTags) { await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); await RecomputeTagsForegroundAsync(initialTags, cancellationToken).ConfigureAwait(false); - }, _dataSource.EventChangeDelay, _asyncListener.BeginAsyncOperation(nameof(EnqueueWork)), cancellationToken); + }, _dataSource.EventChangeDelay, _asyncListener, _asyncListener.BeginAsyncOperation(nameof(EnqueueWork)), cancellationToken); } /// diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs index 3437033b7809f..a7cb189fe08ba 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs @@ -42,12 +42,12 @@ private void OnTagsChangedForBuffer( } } - private Task ProcessTagsChangedAsync( + private ValueTask ProcessTagsChangedAsync( ImmutableArray snapshotSpans, CancellationToken cancellationToken) { var tagsChanged = this.TagsChanged; if (tagsChanged == null) - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; foreach (var collection in snapshotSpans) { @@ -65,7 +65,7 @@ private Task ProcessTagsChangedAsync( tagsChanged(this, new SnapshotSpanEventArgs(span)); } - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; } } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2+Test.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2+Test.cs index 6ac0276e90786..a53c5d8cfd2c0 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2+Test.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2+Test.cs @@ -13,6 +13,9 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing.Verifiers; using Xunit; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Text; #if !CODE_STYLE using Roslyn.Utilities; @@ -26,6 +29,8 @@ public static partial class CSharpCodeFixVerifier { public class Test : CSharpCodeFixTest { + private readonly SharedVerifierState _sharedState; + static Test() { // If we have outdated defaults from the host unit test application targeting an older .NET Framework, use more @@ -42,6 +47,8 @@ static Test() public Test() { + _sharedState = new SharedVerifierState(this, DefaultFileExt); + MarkupOptions = Testing.MarkupOptions.UseFirstDescriptor; SolutionTransforms.Add((solution, projectId) => @@ -53,26 +60,6 @@ public Test() compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); - var (analyzerConfigSource, remainingOptions) = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(DefaultFileExt, EditorConfig, Options); - if (analyzerConfigSource is object) - { - foreach (var id in solution.ProjectIds) - { - var documentId = DocumentId.CreateNewId(id, ".editorconfig"); - solution = solution.AddAnalyzerConfigDocument(documentId, ".editorconfig", analyzerConfigSource, filePath: "/.editorconfig"); - } - } - -#if !CODE_STYLE - var options = solution.Options; - foreach (var (key, value) in remainingOptions) - { - options = options.WithChangedOption(key, value); - } - - solution = solution.WithOptions(options); -#endif - return solution; }); } @@ -83,24 +70,29 @@ public Test() /// public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.CSharp8; - /// - /// Gets a collection of options to apply to for testing. Values may be added - /// using a collection initializer. - /// - internal OptionsCollection Options { get; } = new OptionsCollection(LanguageNames.CSharp); + /// + internal OptionsCollection Options => _sharedState.Options; - public string? EditorConfig { get; set; } + /// + public string? EditorConfig + { + get => _sharedState.EditorConfig; + set => _sharedState.EditorConfig = value; + } public Func, Diagnostic?>? DiagnosticSelector { get; set; } - public override async Task RunAsync(CancellationToken cancellationToken = default) + public Action>? CodeActionsVerifier { get; set; } + + protected override async Task RunImplAsync(CancellationToken cancellationToken = default) { if (DiagnosticSelector is object) { Assert.True(CodeFixTestBehaviors.HasFlag(Testing.CodeFixTestBehaviors.FixOne), $"'{nameof(DiagnosticSelector)}' can only be used with '{nameof(Testing.CodeFixTestBehaviors)}.{nameof(Testing.CodeFixTestBehaviors.FixOne)}'"); } - await base.RunAsync(cancellationToken); + _sharedState.Apply(); + await base.RunImplAsync(cancellationToken); } #if !CODE_STYLE @@ -113,6 +105,12 @@ protected override AnalyzerOptions GetAnalyzerOptions(Project project) return DiagnosticSelector?.Invoke(fixableDiagnostics) ?? base.TrySelectDiagnosticToFix(fixableDiagnostics); } + + protected override ImmutableArray FilterCodeActions(ImmutableArray actions) + { + CodeActionsVerifier?.Invoke(actions); + return base.FilterCodeActions(actions); + } } } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs index ac1137fc4802f..8012ac3db1bef 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Net; using System.Threading; @@ -28,6 +27,8 @@ public static partial class CSharpCodeRefactoringVerifier { public class Test : CSharpCodeRefactoringTest { + private readonly SharedVerifierState _sharedState; + static Test() { // If we have outdated defaults from the host unit test application targeting an older .NET Framework, use more @@ -44,6 +45,8 @@ static Test() public Test() { + _sharedState = new SharedVerifierState(this, DefaultFileExt); + SolutionTransforms.Add((solution, projectId) => { var parseOptions = (CSharpParseOptions)solution.GetProject(projectId)!.ParseOptions!; @@ -53,26 +56,6 @@ public Test() compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); - var (analyzerConfigSource, remainingOptions) = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(DefaultFileExt, EditorConfig, Options); - if (analyzerConfigSource is object) - { - foreach (var id in solution.ProjectIds) - { - var documentId = DocumentId.CreateNewId(id, ".editorconfig"); - solution = solution.AddAnalyzerConfigDocument(documentId, ".editorconfig", analyzerConfigSource, filePath: "/.editorconfig"); - } - } - -#if !CODE_STYLE - var options = solution.Options; - foreach (var (key, value) in remainingOptions) - { - options = options.WithChangedOption(key, value); - } - - solution = solution.WithOptions(options); -#endif - return solution; }); } @@ -83,13 +66,15 @@ public Test() /// public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.CSharp8; - /// - /// Gets a collection of options to apply to for testing. Values may be added - /// using a collection initializer. - /// - internal OptionsCollection Options { get; } = new OptionsCollection(LanguageNames.CSharp); + /// + internal OptionsCollection Options => _sharedState.Options; - public string? EditorConfig { get; set; } + /// + public string? EditorConfig + { + get => _sharedState.EditorConfig; + set => _sharedState.EditorConfig = value; + } /// /// The set of code action s offered the user in this exact order. @@ -97,6 +82,12 @@ public Test() /// public string[]? ExactActionSetOffered { get; set; } + protected override async Task RunImplAsync(CancellationToken cancellationToken) + { + _sharedState.Apply(); + await base.RunImplAsync(cancellationToken); + } + protected override ImmutableArray FilterCodeActions(ImmutableArray actions) { var result = base.FilterCodeActions(actions); @@ -110,8 +101,6 @@ protected override ImmutableArray FilterCodeActions(ImmutableArray _workspaces = new(); - protected override AnalyzerOptions GetAnalyzerOptions(Project project) => new WorkspaceAnalyzerOptions(base.GetAnalyzerOptions(project), project.Solution); @@ -122,38 +111,15 @@ protected override AnalyzerOptions GetAnalyzerOptions(Project project) private static readonly TestComposition s_editorFeaturesOOPComposition = EditorTestCompositions.EditorFeatures.WithTestHostParts(TestHost.OutOfProcess); - public override AdhocWorkspace CreateWorkspace() + protected override Workspace CreateWorkspaceImpl() { if (TestHost == TestHost.InProcess) - return base.CreateWorkspace(); + return base.CreateWorkspaceImpl(); var hostServices = s_editorFeaturesOOPComposition.GetHostServices(); var workspace = new AdhocWorkspace(hostServices); - lock (_workspaces) - _workspaces.Add(workspace); - return workspace; } - - public override async Task RunAsync(CancellationToken cancellationToken = default) - { - try - { - await base.RunAsync(cancellationToken).ConfigureAwait(false); - } - finally - { - var workspaces = new List(); - lock (_workspaces) - { - workspaces.AddRange(_workspaces); - _workspaces.Clear(); - } - - foreach (var workspace in workspaces) - workspace.Dispose(); - } - } #endif } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs new file mode 100644 index 0000000000000..c8679afa8e11c --- /dev/null +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +#if !CODE_STYLE +using Roslyn.Utilities; +#endif + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions +{ + internal sealed class SharedVerifierState + { + private readonly AnalyzerTest _test; + private readonly string _defaultFileExt; + + /// + /// The index in of the generated + /// .editorconfig file for , or if no such + /// file has been generated yet. + /// + private int? _analyzerConfigIndex; + + /// + /// The index in of the options transformation for + /// remaining , or if no such transfor has been generated yet. + /// + private Func? _remainingOptionsSolutionTransform; + + public SharedVerifierState(AnalyzerTest test, string defaultFileExt) + { + _test = test; + _defaultFileExt = defaultFileExt; + Options = new OptionsCollection(test.Language); + } + + public string? EditorConfig { get; set; } + + /// + /// Gets a collection of options to apply to for testing. Values may be added + /// using a collection initializer. + /// + internal OptionsCollection Options { get; } + + internal void Apply() + { + var (analyzerConfigSource, remainingOptions) = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(_defaultFileExt, EditorConfig, Options); + if (analyzerConfigSource is not null) + { + if (_analyzerConfigIndex is null) + { + _analyzerConfigIndex = _test.TestState.AnalyzerConfigFiles.Count; + _test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", analyzerConfigSource)); + } + else + { + _test.TestState.AnalyzerConfigFiles[_analyzerConfigIndex.Value] = ("/.editorconfig", analyzerConfigSource); + } + } + else if (_analyzerConfigIndex is { } index) + { + _analyzerConfigIndex = null; + _test.TestState.AnalyzerConfigFiles.RemoveAt(index); + } + + var solutionTransformIndex = _remainingOptionsSolutionTransform is not null ? _test.SolutionTransforms.IndexOf(_remainingOptionsSolutionTransform) : -1; + if (remainingOptions is not null) + { + // Generate a new solution transform + _remainingOptionsSolutionTransform = (solution, projectId) => + { +#if !CODE_STYLE + var options = solution.Options; + foreach (var (key, value) in remainingOptions) + { + options = options.WithChangedOption(key, value); + } + + solution = solution.WithOptions(options); +#endif + + return solution; + }; + + if (solutionTransformIndex < 0) + { + _test.SolutionTransforms.Add(_remainingOptionsSolutionTransform); + } + else + { + _test.SolutionTransforms[solutionTransformIndex] = _remainingOptionsSolutionTransform; + } + } + else if (_remainingOptionsSolutionTransform is not null) + { + _test.SolutionTransforms.Remove(_remainingOptionsSolutionTransform); + _remainingOptionsSolutionTransform = null; + } + } + } +} diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeFixVerifier`2+Test.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeFixVerifier`2+Test.cs index d63b2636931c9..1ec477ed7b4a6 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeFixVerifier`2+Test.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeFixVerifier`2+Test.cs @@ -26,6 +26,8 @@ public static partial class VisualBasicCodeFixVerifier { public class Test : VisualBasicCodeFixTest { + private readonly SharedVerifierState _sharedState; + static Test() { // If we have outdated defaults from the host unit test application targeting an older .NET Framework, use more @@ -42,6 +44,8 @@ static Test() public Test() { + _sharedState = new SharedVerifierState(this, DefaultFileExt); + MarkupOptions = Testing.MarkupOptions.UseFirstDescriptor; SolutionTransforms.Add((solution, projectId) => @@ -49,26 +53,6 @@ public Test() var parseOptions = (VisualBasicParseOptions)solution.GetProject(projectId)!.ParseOptions!; solution = solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion)); - var (analyzerConfigSource, remainingOptions) = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(DefaultFileExt, EditorConfig, Options); - if (analyzerConfigSource is object) - { - foreach (var id in solution.ProjectIds) - { - var documentId = DocumentId.CreateNewId(id, ".editorconfig"); - solution = solution.AddAnalyzerConfigDocument(documentId, ".editorconfig", analyzerConfigSource, filePath: "/.editorconfig"); - } - } - -#if !CODE_STYLE - var options = solution.Options; - foreach (var (key, value) in remainingOptions) - { - options = options.WithChangedOption(key, value); - } - - solution = solution.WithOptions(options); -#endif - return solution; }); } @@ -79,24 +63,27 @@ public Test() /// public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.VisualBasic16; - /// - /// Gets a collection of options to apply to for testing. Values may be added - /// using a collection initializer. - /// - internal OptionsCollection Options { get; } = new OptionsCollection(LanguageNames.VisualBasic); + /// + internal OptionsCollection Options => _sharedState.Options; - public string? EditorConfig { get; set; } + /// + public string? EditorConfig + { + get => _sharedState.EditorConfig; + set => _sharedState.EditorConfig = value; + } public Func, Diagnostic?>? DiagnosticSelector { get; set; } - public override async Task RunAsync(CancellationToken cancellationToken = default) + protected override async Task RunImplAsync(CancellationToken cancellationToken = default) { if (DiagnosticSelector is object) { Assert.True(CodeFixTestBehaviors.HasFlag(Testing.CodeFixTestBehaviors.FixOne), $"'{nameof(DiagnosticSelector)}' can only be used with '{nameof(Testing.CodeFixTestBehaviors)}.{nameof(Testing.CodeFixTestBehaviors.FixOne)}'"); } - await base.RunAsync(cancellationToken); + _sharedState.Apply(); + await base.RunImplAsync(cancellationToken); } #if !CODE_STYLE diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs index 0a20d8d15b116..727be29d9a845 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.CodeAnalysis.VisualBasic.Testing; -using Microsoft.CodeAnalysis.Testing.Verifiers; -using Microsoft.CodeAnalysis.CodeRefactorings; #if !CODE_STYLE using System; @@ -21,6 +23,8 @@ public static partial class VisualBasicCodeRefactoringVerifier { public class Test : VisualBasicCodeRefactoringTest { + private readonly SharedVerifierState _sharedState; + static Test() { // If we have outdated defaults from the host unit test application targeting an older .NET Framework, use more @@ -37,31 +41,13 @@ static Test() public Test() { + _sharedState = new SharedVerifierState(this, DefaultFileExt); + SolutionTransforms.Add((solution, projectId) => { var parseOptions = (VisualBasicParseOptions)solution.GetProject(projectId)!.ParseOptions!; solution = solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion)); - var (analyzerConfigSource, remainingOptions) = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(DefaultFileExt, EditorConfig, Options); - if (analyzerConfigSource is object) - { - foreach (var id in solution.ProjectIds) - { - var documentId = DocumentId.CreateNewId(id, ".editorconfig"); - solution = solution.AddAnalyzerConfigDocument(documentId, ".editorconfig", analyzerConfigSource, filePath: "/.editorconfig"); - } - } - -#if !CODE_STYLE - var options = solution.Options; - foreach (var (key, value) in remainingOptions) - { - options = options.WithChangedOption(key, value); - } - - solution = solution.WithOptions(options); -#endif - return solution; }); } @@ -72,13 +58,21 @@ public Test() /// public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.VisualBasic16; - /// - /// Gets a collection of options to apply to for testing. Values may be added - /// using a collection initializer. - /// - internal OptionsCollection Options { get; } = new OptionsCollection(LanguageNames.VisualBasic); + /// + internal OptionsCollection Options => _sharedState.Options; - public string? EditorConfig { get; set; } + /// + public string? EditorConfig + { + get => _sharedState.EditorConfig; + set => _sharedState.EditorConfig = value; + } + + protected override async Task RunImplAsync(CancellationToken cancellationToken) + { + _sharedState.Apply(); + await base.RunImplAsync(cancellationToken); + } #if !CODE_STYLE protected override AnalyzerOptions GetAnalyzerOptions(Project project) diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs index e6385ffafa1e7..fbf4d02990764 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs @@ -59,6 +59,7 @@ internal virtual bool ShouldSkipMessageDescriptionVerification(DiagnosticDescrip return true; } } + return false; } diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.Shared.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.Shared.cs index 81e2cfb3ee098..40cbb9df11e6a 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.Shared.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.Shared.cs @@ -705,7 +705,7 @@ public class A public static abstract string Property1 { get; } public virtual string Property { get; } - public abstract static void Method2(); + public static abstract void Method2(); public virtual void Method1(); } diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 559dbdedb94e3..936471ea40450 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -402,6 +402,9 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE0100 dotnet_diagnostic.IDE0100.severity = %value% +# IDE0150 +dotnet_diagnostic.IDE0150.severity = %value% + # IDE1005 dotnet_diagnostic.IDE1005.severity = %value% @@ -989,6 +992,9 @@ No editorconfig based code style option # IDE0130, PreferNamespaceAndFolderMatchStructure dotnet_style_namespace_match_folder = true +# IDE0150 +No editorconfig based code style option + # IDE1005, PreferConditionalDelegateCall csharp_style_conditional_delegate_call = true diff --git a/src/EditorFeatures/Test/DocCommentFormatting/DocCommentFormattingTests.cs b/src/EditorFeatures/Test/DocCommentFormatting/DocCommentFormattingTests.cs index 9a23741881b7d..3d8d8e9e73134 100644 --- a/src/EditorFeatures/Test/DocCommentFormatting/DocCommentFormattingTests.cs +++ b/src/EditorFeatures/Test/DocCommentFormatting/DocCommentFormattingTests.cs @@ -50,7 +50,7 @@ public void ExampleAndCodeTags() results in p's having the value (2,8). "; - var expected = "This method changes the point's location by the given x- and y-offsets. For example:\r\n\r\nPoint p = new Point(3,5); p.Translate(-1,3);\r\n\r\nresults in p's having the value (2,8)."; + var expected = "This method changes the point's location by the given x- and y-offsets. For example:\r\n\r\n Point p = new Point(3,5);\r\n p.Translate(-1,3);\r\n \r\n\r\nresults in p's having the value (2,8)."; TestFormat(comment, expected); } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 6f9478481297d..c431c78d5336b 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -1905,6 +1905,7 @@ void ValidateDelta(ManagedModuleUpdate delta) Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(0x06000001, delta.UpdatedMethods.Single()); + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); Assert.Equal(moduleId, delta.Module); Assert.Empty(delta.ExceptionRegions); Assert.Empty(delta.SequencePoints); @@ -2042,6 +2043,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(0x06000002, delta.UpdatedMethods.Single()); + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); Assert.Equal(moduleId, delta.Module); Assert.Empty(delta.ExceptionRegions); Assert.Empty(delta.SequencePoints); @@ -2157,6 +2159,7 @@ partial class C { int Y = 2; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(6, delta.UpdatedMethods.Length); // F, C.C(), D.D(), E.E(int), E.E(int, int), lambda + AssertEx.SetEqual(new[] { 0x02000002, 0x02000003, 0x02000004, 0x02000005 }, delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X")); EndDebuggingSession(debuggingSession); } @@ -2271,6 +2274,7 @@ class C { int Y => 2; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(2, delta.UpdatedMethods.Length); + AssertEx.Equal(new[] { 0x02000002, 0x02000003 }, delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X")); EndDebuggingSession(debuggingSession); } @@ -2332,6 +2336,7 @@ int M() Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Empty(delta.UpdatedMethods); + Assert.Empty(delta.UpdatedTypes); EndDebuggingSession(debuggingSession); } @@ -2375,6 +2380,7 @@ partial class C { int X = 1; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); // constructor update + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -2421,6 +2427,7 @@ class C { int Y => 1; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); + Assert.Equal(0x02000003, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -2463,6 +2470,7 @@ class C { int Y => 1; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); + Assert.Equal(0x02000003, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -2502,6 +2510,7 @@ public async Task BreakMode_ValidSignificantChange_SourceGenerators_DocumentRemo Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -3191,6 +3200,7 @@ void F() Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Empty(delta.UpdatedMethods); + Assert.Empty(delta.UpdatedTypes); AssertEx.Equal(new[] { @@ -3260,6 +3270,7 @@ static void F() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3278,6 +3289,7 @@ static void F() (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3305,6 +3317,7 @@ static void F() (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3431,6 +3444,7 @@ static void F() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3489,6 +3503,7 @@ static void F() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3625,6 +3640,7 @@ public async Task WatchHotReloadServiceTest() var result = await hotReload.EmitSolutionUpdateAsync(solution, CancellationToken.None); Assert.Empty(result.diagnostics); Assert.Equal(1, result.updates.Length); + AssertEx.Equal(new[] { 0x02000002 }, result.updates[0].UpdatedTypes); solution = solution.WithDocumentText(documentIdA, SourceText.From(source3, Encoding.UTF8)); @@ -3666,7 +3682,7 @@ public async Task UnitTestingHotReloadServiceTest() var hotReload = new UnitTestingHotReloadService(workspace.Services); - await hotReload.StartSessionAsync(solution, CancellationToken.None); + await hotReload.StartSessionAsync(solution, ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition"), CancellationToken.None); var sessionId = hotReload.GetTestAccessor().SessionId; var session = encService.GetTestAccessor().GetDebuggingSession(sessionId); @@ -3678,13 +3694,13 @@ public async Task UnitTestingHotReloadServiceTest() solution = solution.WithDocumentText(documentIdA, SourceText.From(source2, Encoding.UTF8)); - var result = await hotReload.EmitSolutionUpdateAsync(solution, CancellationToken.None); + var result = await hotReload.EmitSolutionUpdateAsync(solution, true, CancellationToken.None); Assert.Empty(result.diagnostics); Assert.Equal(1, result.updates.Length); solution = solution.WithDocumentText(documentIdA, SourceText.From(source3, Encoding.UTF8)); - result = await hotReload.EmitSolutionUpdateAsync(solution, CancellationToken.None); + result = await hotReload.EmitSolutionUpdateAsync(solution, true, CancellationToken.None); AssertEx.Equal( new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, result.diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); diff --git a/src/EditorFeatures/Test/EditorAdapter/SpanExtensionsTest.cs b/src/EditorFeatures/Test/EditorAdapter/SpanExtensionsTest.cs index 2f89cdba54c7f..edb1dcc2413ea 100644 --- a/src/EditorFeatures/Test/EditorAdapter/SpanExtensionsTest.cs +++ b/src/EditorFeatures/Test/EditorAdapter/SpanExtensionsTest.cs @@ -25,6 +25,7 @@ static void del(int start, int length) Assert.Equal(start, textSpan.Start); Assert.Equal(length, textSpan.Length); } + del(0, 5); del(10, 15); } diff --git a/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs b/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs index 5dd0a86442d4c..da73cc155115a 100644 --- a/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs +++ b/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs @@ -296,7 +296,7 @@ public class Bar : IEnumerable memberName: "class Bar", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IEnumerable", - relationship: InheritanceRelationship.Implementing, + relationship: InheritanceRelationship.ImplementedInterface, inMetadata: true))); var itemForGetEnumerator = new TestInheritanceMemberItem( @@ -304,7 +304,7 @@ public class Bar : IEnumerable memberName: "IEnumerator Bar.GetEnumerator()", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "IEnumerator IEnumerable.GetEnumerator()", - relationship: InheritanceRelationship.Implementing, + relationship: InheritanceRelationship.ImplementedMember, inMetadata: true))); return VerifyInSingleDocumentAsync(markup, LanguageNames.CSharp, itemForBar, itemForGetEnumerator); @@ -326,7 +326,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar", locationTag: "target2", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemOnLine3 = new TestInheritanceMemberItem( lineNumber: 3, @@ -334,7 +334,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target1", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); return VerifyInSingleDocumentAsync( markup, @@ -347,9 +347,9 @@ public class {|target2:Bar|} : IBar public Task TestCSharpInterfaceImplementingInterface() { var markup = @" -interface {|target1:IBar|} { } -interface {|target2:IBar2|} : IBar { } - "; + interface {|target1:IBar|} { } + interface {|target2:IBar2|} : IBar { } + "; var itemOnLine2 = new TestInheritanceMemberItem( lineNumber: 2, @@ -357,7 +357,7 @@ interface {|target2:IBar2|} : IBar { } targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar2", locationTag: "target2", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.ImplementingType)) ); var itemOnLine3 = new TestInheritanceMemberItem( lineNumber: 3, @@ -366,7 +366,7 @@ interface {|target2:IBar2|} : IBar { } .Add(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target1", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.InheritedInterface)) ); return VerifyInSingleDocumentAsync( @@ -380,9 +380,9 @@ interface {|target2:IBar2|} : IBar { } public Task TestCSharpClassInheritsClass() { var markup = @" -class {|target2:A|} { } -class {|target1:B|} : A { } - "; + class {|target2:A|} { } + class {|target1:B|} : A { } + "; var itemOnLine2 = new TestInheritanceMemberItem( lineNumber: 2, @@ -390,7 +390,7 @@ class {|target1:B|} : A { } targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class B", locationTag: "target1", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.DerivedType)) ); var itemOnLine3 = new TestInheritanceMemberItem( lineNumber: 3, @@ -398,7 +398,7 @@ class {|target1:B|} : A { } targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class A", locationTag: "target2", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.BaseType)) ); return VerifyInSingleDocumentAsync( @@ -416,9 +416,9 @@ class {|target1:B|} : A { } public Task TestCSharpTypeWithoutBaseType(string typeName) { var markup = $@" -public {typeName} Bar -{{ -}}"; + public {typeName} Bar + {{ + }}"; return VerifyNoItemForDocumentAsync(markup, LanguageNames.CSharp); } @@ -430,12 +430,12 @@ public Task TestCSharpTypeWithoutBaseType(string typeName) public Task TestCSharpSpecialMember(string memberDeclaration) { var markup = $@" -public abstract class {{|target1:Bar1|}} -{{}} -public class Bar : Bar1 -{{ - {{|{SearchAreaTag}:{memberDeclaration}|}} -}}"; + public abstract class {{|target1:Bar1|}} + {{}} + public class Bar : Bar1 + {{ + {{|{SearchAreaTag}:{memberDeclaration}|}} + }}"; return VerifyInSingleDocumentAsync( markup, LanguageNames.CSharp, @@ -446,53 +446,32 @@ public class Bar : Bar1 new TargetInfo( targetSymbolDisplayName: "class Bar1", locationTag: "target1", - relationship: InheritanceRelationship.Implementing)))); + relationship: InheritanceRelationship.BaseType)))); } [Fact] - public Task TestCSharpMetadataInterface() + public Task TestCSharpEventDeclaration() { var markup = @" -using System.Collections; -public class Bar : IEnumerable -{ -}"; - return VerifyInSingleDocumentAsync( - markup, - LanguageNames.CSharp, - new TestInheritanceMemberItem( - lineNumber: 3, - memberName: "class Bar", - targets: ImmutableArray.Create( - new TargetInfo( - targetSymbolDisplayName: "interface IEnumerable", - relationship: InheritanceRelationship.Implementing, - inMetadata: true)))); + using System; + interface {|target2:IBar|} + { + event EventHandler {|target4:e|}; } - - [Fact] - public Task TestCSharpEventDeclaration() + public class {|target1:Bar|} : IBar { - var markup = @" -using System; -interface {|target2:IBar|} -{ - event EventHandler {|target4:e|}; -} -public class {|target1:Bar|} : IBar -{ - public event EventHandler {|target3:e|} - { - add {} remove {} - } -}"; + public event EventHandler {|target3:e|} + { + add {} remove {} + } + }"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 3, memberName: "interface IBar", ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 7, @@ -500,7 +479,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForEventInInterface = new TestInheritanceMemberItem( lineNumber: 5, @@ -508,7 +487,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler Bar.e", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForEventInClass = new TestInheritanceMemberItem( lineNumber: 9, @@ -516,7 +495,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler IBar.e", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -531,21 +510,21 @@ public class {|target1:Bar|} : IBar public Task TestCSharpEventFieldDeclarations() { var markup = @"using System; -interface {|target2:IBar|} -{ - event EventHandler {|target5:e1|}, {|target6:e2|}; -} -public class {|target1:Bar|} : IBar -{ - public event EventHandler {|target3:e1|}, {|target4:e2|}; -}"; + interface {|target2:IBar|} + { + event EventHandler {|target5:e1|}, {|target6:e2|}; + } + public class {|target1:Bar|} : IBar + { + public event EventHandler {|target3:e1|}, {|target4:e2|}; + }"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, memberName: "interface IBar", ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 6, @@ -553,7 +532,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForE1InInterface = new TestInheritanceMemberItem( lineNumber: 4, @@ -561,7 +540,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler Bar.e1", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForE2InInterface = new TestInheritanceMemberItem( lineNumber: 4, @@ -569,7 +548,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler Bar.e2", locationTag: "target4", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForE1InClass = new TestInheritanceMemberItem( lineNumber: 8, @@ -577,7 +556,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler IBar.e1", locationTag: "target5", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); var itemForE2InClass = new TestInheritanceMemberItem( lineNumber: 8, @@ -585,7 +564,7 @@ public class {|target1:Bar|} : IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler IBar.e2", locationTag: "target6", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -602,27 +581,27 @@ public class {|target1:Bar|} : IBar public Task TestCSharpInterfaceMembers() { var markup = @"using System; -interface {|target1:IBar|} -{ - void {|target4:Foo|}(); - int {|target6:Poo|} { get; set; } - event EventHandler {|target8:Eoo|}; - int {|target9:this|}[int i] { get; set; } -} -public class {|target2:Bar|} : IBar -{ - public void {|target3:Foo|}() { } - public int {|target5:Poo|} { get; set; } - public event EventHandler {|target7:Eoo|}; - public int {|target10:this|}[int i] { get => 1; set { } } -}"; + interface {|target1:IBar|} + { + void {|target4:Foo|}(); + int {|target6:Poo|} { get; set; } + event EventHandler {|target8:Eoo|}; + int {|target9:this|}[int i] { get; set; } + } + public class {|target2:Bar|} : IBar + { + public void {|target3:Foo|}() { } + public int {|target5:Poo|} { get; set; } + public event EventHandler {|target7:Eoo|}; + public int {|target10:this|}[int i] { get => 1; set { } } + }"; var itemForEooInClass = new TestInheritanceMemberItem( lineNumber: 13, memberName: "event EventHandler Bar.Eoo", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler IBar.Eoo", locationTag: "target8", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.ImplementedMember)) ); var itemForEooInInterface = new TestInheritanceMemberItem( @@ -631,7 +610,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "event EventHandler Bar.Eoo", locationTag: "target7", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.ImplementingMember)) ); var itemForPooInInterface = new TestInheritanceMemberItem( @@ -640,7 +619,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "int Bar.Poo { get; set; }", locationTag: "target5", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.ImplementingMember)) ); var itemForPooInClass = new TestInheritanceMemberItem( @@ -649,7 +628,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "int IBar.Poo { get; set; }", locationTag: "target6", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.ImplementedMember)) ); var itemForFooInInterface = new TestInheritanceMemberItem( @@ -658,7 +637,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void Bar.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.ImplementingMember)) ); var itemForFooInClass = new TestInheritanceMemberItem( @@ -667,7 +646,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void IBar.Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.ImplementedMember)) ); var itemForIBar = new TestInheritanceMemberItem( @@ -676,7 +655,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar", locationTag: "target2", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.ImplementingType)) ); var itemForBar = new TestInheritanceMemberItem( @@ -685,7 +664,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target1", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.ImplementedInterface)) ); var itemForIndexerInClass = new TestInheritanceMemberItem( @@ -694,7 +673,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "int IBar.this[int] { get; set; }", locationTag: "target9", - relationship: InheritanceRelationship.Implementing)) + relationship: InheritanceRelationship.ImplementedMember)) ); var itemForIndexerInInterface = new TestInheritanceMemberItem( @@ -703,7 +682,7 @@ public class {|target2:Bar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "int Bar.this[int] { get; set; }", locationTag: "target10", - relationship: InheritanceRelationship.Implemented)) + relationship: InheritanceRelationship.ImplementingMember)) ); return VerifyInSingleDocumentAsync( @@ -727,19 +706,19 @@ public class {|target2:Bar|} : IBar public Task TestCSharpAbstractClassMembers(string modifier) { var markup = $@"using System; -public abstract class {{|target2:Bar|}} -{{ - public {modifier} void {{|target4:Foo|}}(); - public {modifier} int {{|target6:Poo|}} {{ get; set; }} - public {modifier} event EventHandler {{|target8:Eoo|}}; -}} -public class {{|target1:Bar2|}} : Bar -{{ - public override void {{|target3:Foo|}}() {{ }} - public override int {{|target5:Poo|}} {{ get; set; }} - public override event EventHandler {{|target7:Eoo|}}; -}} - "; + public abstract class {{|target2:Bar|}} + {{ + public {modifier} void {{|target4:Foo|}}(); + public {modifier} int {{|target6:Poo|}} {{ get; set; }} + public {modifier} event EventHandler {{|target8:Eoo|}}; + }} + public class {{|target1:Bar2|}} : Bar + {{ + public override void {{|target3:Foo|}}() {{ }} + public override int {{|target5:Poo|}} {{ get; set; }} + public override event EventHandler {{|target7:Eoo|}}; + }} + "; var itemForEooInClass = new TestInheritanceMemberItem( lineNumber: 12, @@ -747,7 +726,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: $"{modifier} event EventHandler Bar.Eoo", locationTag: "target8", - relationship: InheritanceRelationship.Overriding))); + relationship: InheritanceRelationship.OverriddenMember))); var itemForEooInAbstractClass = new TestInheritanceMemberItem( lineNumber: 6, @@ -755,7 +734,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "override event EventHandler Bar2.Eoo", locationTag: "target7", - relationship: InheritanceRelationship.Overridden))); + relationship: InheritanceRelationship.OverridingMember))); var itemForPooInClass = new TestInheritanceMemberItem( lineNumber: 11, @@ -763,7 +742,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: $"{modifier} int Bar.Poo {{ get; set; }}", locationTag: "target6", - relationship: InheritanceRelationship.Overriding))); + relationship: InheritanceRelationship.OverriddenMember))); var itemForPooInAbstractClass = new TestInheritanceMemberItem( lineNumber: 5, @@ -771,7 +750,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "override int Bar2.Poo { get; set; }", locationTag: "target5", - relationship: InheritanceRelationship.Overridden))); + relationship: InheritanceRelationship.OverridingMember))); var itemForFooInAbstractClass = new TestInheritanceMemberItem( lineNumber: 4, @@ -779,7 +758,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "override void Bar2.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Overridden))); + relationship: InheritanceRelationship.OverridingMember))); var itemForFooInClass = new TestInheritanceMemberItem( lineNumber: 10, @@ -787,7 +766,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: $"{modifier} void Bar.Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Overriding))); + relationship: InheritanceRelationship.OverriddenMember))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 2, @@ -795,7 +774,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar2", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.DerivedType))); var itemForBar2 = new TestInheritanceMemberItem( lineNumber: 8, @@ -803,7 +782,7 @@ public class {{|target1:Bar2|}} : Bar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.BaseType))); return VerifyInSingleDocumentAsync( markup, @@ -823,32 +802,32 @@ public class {{|target1:Bar2|}} : Bar public Task TestCSharpOverrideMemberCanFindImplementingInterface(bool testDuplicate) { var markup1 = @"using System; -public interface {|target4:IBar|} -{ - void {|target6:Foo|}(); -} -public class {|target1:Bar1|} : IBar -{ - public virtual void {|target2:Foo|}() { } -} -public class {|target5:Bar2|} : Bar1 -{ - public override void {|target3:Foo|}() { } -}"; + public interface {|target4:IBar|} + { + void {|target6:Foo|}(); + } + public class {|target1:Bar1|} : IBar + { + public virtual void {|target2:Foo|}() { } + } + public class {|target5:Bar2|} : Bar1 + { + public override void {|target3:Foo|}() { } + }"; var markup2 = @"using System; -public interface {|target4:IBar|} -{ - void {|target6:Foo|}(); -} -public class {|target1:Bar1|} : IBar -{ - public virtual void {|target2:Foo|}() { } -} -public class {|target5:Bar2|} : Bar1, IBar -{ - public override void {|target3:Foo|}() { } -}"; + public interface {|target4:IBar|} + { + void {|target6:Foo|}(); + } + public class {|target1:Bar1|} : IBar + { + public virtual void {|target2:Foo|}() { } + } + public class {|target5:Bar2|} : Bar1, IBar + { + public override void {|target3:Foo|}() { } + }"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, @@ -856,11 +835,11 @@ public class {|target5:Bar2|} : Bar1, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar1", locationTag: "target1", - relationship: InheritanceRelationship.Implemented), + relationship: InheritanceRelationship.ImplementingType), new TargetInfo( targetSymbolDisplayName: "class Bar2", locationTag: "target5", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInIBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -868,11 +847,11 @@ public class {|target5:Bar2|} : Bar1, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "virtual void Bar1.Foo()", locationTag: "target2", - relationship: InheritanceRelationship.Implemented), + relationship: InheritanceRelationship.ImplementingMember), new TargetInfo( targetSymbolDisplayName: "override void Bar2.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForBar1 = new TestInheritanceMemberItem( lineNumber: 6, @@ -880,11 +859,11 @@ public class {|target5:Bar2|} : Bar1, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target4", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.ImplementedInterface), new TargetInfo( targetSymbolDisplayName: "class Bar2", locationTag: "target5", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.DerivedType))); var itemForFooInBar1 = new TestInheritanceMemberItem( lineNumber: 8, @@ -892,11 +871,11 @@ public class {|target5:Bar2|} : Bar1, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void IBar.Foo()", locationTag: "target6", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.ImplementedMember), new TargetInfo( targetSymbolDisplayName: "override void Bar2.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Overridden))); + relationship: InheritanceRelationship.OverridingMember))); var itemForBar2 = new TestInheritanceMemberItem( lineNumber: 10, @@ -905,11 +884,11 @@ public class {|target5:Bar2|} : Bar1, IBar new TargetInfo( targetSymbolDisplayName: "class Bar1", locationTag: "target1", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.BaseType), new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForFooInBar2 = new TestInheritanceMemberItem( lineNumber: 12, @@ -917,11 +896,11 @@ public class {|target5:Bar2|} : Bar1, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void IBar.Foo()", locationTag: "target6", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.ImplementedMember), new TargetInfo( targetSymbolDisplayName: "virtual void Bar1.Foo()", locationTag: "target2", - relationship: InheritanceRelationship.Overriding))); + relationship: InheritanceRelationship.OverriddenMember))); return VerifyInSingleDocumentAsync( testDuplicate ? markup2 : markup1, @@ -954,7 +933,7 @@ public class {|target1:Bar2|} : IBar, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar2", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInIBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -962,7 +941,7 @@ public class {|target1:Bar2|} : IBar, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void Bar2.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); // Only have one IBar item var itemForBar2 = new TestInheritanceMemberItem( @@ -971,7 +950,7 @@ public class {|target1:Bar2|} : IBar, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); // Only have one IBar.Foo item var itemForFooInBar2 = new TestInheritanceMemberItem( @@ -980,7 +959,7 @@ public class {|target1:Bar2|} : IBar, IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void IBar.Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -1013,7 +992,7 @@ abstract class {|target1:AbsBar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class AbsBar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInIBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -1021,7 +1000,7 @@ abstract class {|target1:AbsBar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void AbsBar.IBar.Foo(int)", locationTag: "target4", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForAbsBar = new TestInheritanceMemberItem( lineNumber: 7, @@ -1029,7 +1008,7 @@ abstract class {|target1:AbsBar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForFooInAbsBar = new TestInheritanceMemberItem( lineNumber: 9, @@ -1037,7 +1016,8 @@ abstract class {|target1:AbsBar|} : IBar targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void IBar.Foo(T)", locationTag: "target3", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember) + )); return VerifyInSingleDocumentAsync( markup, @@ -1048,6 +1028,140 @@ abstract class {|target1:AbsBar|} : IBar itemForFooInAbsBar); } + [Fact] + public Task TestStaticAbstractMemberInterface() + { + var markup = @" +interface {|target5:I1|} where T : I1 +{ + static abstract void {|target4:M1|}(); + static abstract int {|target7:P1|} { get; set; } + static abstract event EventHandler {|target9:e1|}; + static abstract int operator {|target11:+|}(T i1); + static abstract implicit operator {|target12:int|}(T i1); +} + +public class {|target1:Class1|} : I1 +{ + public static void {|target2:M1|}() {} + public static int {|target6:P1|} { get => 1; set { } } + public static event EventHandler {|target8:e1|}; + public static int operator {|target10:+|}(Class1 i) => 1; + public static implicit operator {|target13:int|}(Class1 i) => 0; +}"; + var itemForI1 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface I1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Class1", + locationTag: "target1", + relationship: InheritanceRelationship.ImplementingType))); + + var itemForM1InI1 = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "void I1.M1()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "static void Class1.M1()", + locationTag: "target2", + relationship: InheritanceRelationship.ImplementingMember))); + + var itemForAbsClass1 = new TestInheritanceMemberItem( + lineNumber: 11, + memberName: "class Class1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface I1", + locationTag: "target5", + relationship: InheritanceRelationship.ImplementedInterface))); + + var itemForM1InClass1 = new TestInheritanceMemberItem( + lineNumber: 13, + memberName: "static void Class1.M1()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void I1.M1()", + locationTag: "target4", + relationship: InheritanceRelationship.ImplementedMember))); + + var itemForP1InI1 = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "int I1.P1 { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "static int Class1.P1 { get; set; }", + locationTag: "target6", + relationship: InheritanceRelationship.ImplementingMember))); + + var itemForP1InClass1 = new TestInheritanceMemberItem( + lineNumber: 14, + memberName: "static int Class1.P1 { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "int I1.P1 { get; set; }", + locationTag: "target7", + relationship: InheritanceRelationship.ImplementedMember))); + + var itemForE1InI1 = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "event EventHandler I1.e1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "static event EventHandler Class1.e1", + locationTag: "target8", + relationship: InheritanceRelationship.ImplementingMember))); + + var itemForE1InClass1 = new TestInheritanceMemberItem( + lineNumber: 15, + memberName: "static event EventHandler Class1.e1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler I1.e1", + locationTag: "target9", + relationship: InheritanceRelationship.ImplementedMember))); + + var itemForPlusOperatorInI1 = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "int I1.operator +(T)", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "static int Class1.operator +(Class1)", + locationTag: "target10", + relationship: InheritanceRelationship.ImplementingMember))); + + var itemForPlusOperatorInClass1 = new TestInheritanceMemberItem( + lineNumber: 16, + memberName: "static int Class1.operator +(Class1)", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "int I1.operator +(T)", + locationTag: "target11", + relationship: InheritanceRelationship.ImplementedMember))); + + var itemForIntOperatorInI1 = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "I1.implicit operator int(T)", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "static Class1.implicit operator int(Class1)", + locationTag: "target13", + relationship: InheritanceRelationship.ImplementingMember))); + + var itemForIntOperatorInClass1 = new TestInheritanceMemberItem( + lineNumber: 17, + memberName: "static Class1.implicit operator int(Class1)", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "I1.implicit operator int(T)", + locationTag: "target12", + relationship: InheritanceRelationship.ImplementedMember))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemForI1, + itemForAbsClass1, + itemForM1InI1, + itemForM1InClass1, + itemForP1InI1, + itemForP1InClass1, + itemForE1InI1, + itemForE1InClass1, + itemForPlusOperatorInI1, + itemForPlusOperatorInClass1, + itemForIntOperatorInI1, + itemForIntOperatorInClass1); + } + #endregion #region TestsForVisualBasic @@ -1056,11 +1170,11 @@ abstract class {|target1:AbsBar|} : IBar public Task TestVisualBasicWithErrorBaseType() { var markup = @" -Namespace MyNamespace - Public Class Bar - Implements SomethingNotExist - End Class -End Namespace"; + Namespace MyNamespace + Public Class Bar + Implements SomethingNotExist + End Class + End Namespace"; return VerifyNoItemForDocumentAsync(markup, LanguageNames.VisualBasic); } @@ -1069,20 +1183,20 @@ End Class public Task TestVisualBasicReferencingMetadata() { var markup = @" -Namespace MyNamespace - Public Class Bar - Implements System.Collections.IEnumerable - Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator - Throw New NotImplementedException() - End Function - End Class -End Namespace"; + Namespace MyNamespace + Public Class Bar + Implements System.Collections.IEnumerable + Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class + End Namespace"; var itemForBar = new TestInheritanceMemberItem( lineNumber: 3, memberName: "Class Bar", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IEnumerable", - relationship: InheritanceRelationship.Implementing, + relationship: InheritanceRelationship.ImplementedInterface, inMetadata: true))); var itemForGetEnumerator = new TestInheritanceMemberItem( @@ -1090,7 +1204,7 @@ End Class memberName: "Function Bar.GetEnumerator() As IEnumerator", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Function IEnumerable.GetEnumerator() As IEnumerator", - relationship: InheritanceRelationship.Implementing, + relationship: InheritanceRelationship.ImplementedMember, inMetadata: true))); return VerifyInSingleDocumentAsync(markup, LanguageNames.VisualBasic, itemForBar, itemForGetEnumerator); @@ -1100,18 +1214,18 @@ End Class public Task TestVisualBasicClassImplementingInterface() { var markup = @" -Interface {|target2:IBar|} -End Interface -Class {|target1:Bar|} - Implements IBar -End Class"; + Interface {|target2:IBar|} + End Interface + Class {|target1:Bar|} + Implements IBar + End Class"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, memberName: "Interface IBar", ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -1119,7 +1233,7 @@ Implements IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); return VerifyInSingleDocumentAsync( markup, @@ -1132,11 +1246,11 @@ Implements IBar public Task TestVisualBasicInterfaceImplementingInterface() { var markup = @" -Interface {|target2:IBar2|} -End Interface -Interface {|target1:IBar|} - Inherits IBar2 -End Interface"; + Interface {|target2:IBar2|} + End Interface + Interface {|target1:IBar|} + Inherits IBar2 + End Interface"; var itemForIBar2 = new TestInheritanceMemberItem( lineNumber: 2, @@ -1144,7 +1258,7 @@ Inherits IBar2 ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForIBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -1152,7 +1266,7 @@ Inherits IBar2 ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar2", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.InheritedInterface))); return VerifyInSingleDocumentAsync(markup, LanguageNames.VisualBasic, itemForIBar2, itemForIBar); } @@ -1160,11 +1274,11 @@ Inherits IBar2 public Task TestVisualBasicClassInheritsClass() { var markup = @" -Class {|target2:Bar2|} -End Class -Class {|target1:Bar|} - Inherits Bar2 -End Class"; + Class {|target2:Bar2|} + End Class + Class {|target1:Bar|} + Inherits Bar2 + End Class"; var itemForBar2 = new TestInheritanceMemberItem( lineNumber: 2, @@ -1172,7 +1286,7 @@ Inherits Bar2 ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.DerivedType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -1180,7 +1294,7 @@ Inherits Bar2 ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar2", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.BaseType))); return VerifyInSingleDocumentAsync(markup, LanguageNames.VisualBasic, itemForBar2, itemForBar); } @@ -1192,8 +1306,8 @@ Inherits Bar2 public Task TestVisualBasicTypeWithoutBaseType(string typeName) { var markup = $@" -{typeName} Bar -End {typeName}"; + {typeName} Bar + End {typeName}"; return VerifyNoItemForDocumentAsync(markup, LanguageNames.VisualBasic); } @@ -1202,10 +1316,10 @@ public Task TestVisualBasicTypeWithoutBaseType(string typeName) public Task TestVisualBasicMetadataInterface() { var markup = @" -Imports System.Collections -Class Bar - Implements IEnumerable -End Class"; + Imports System.Collections + Class Bar + Implements IEnumerable + End Class"; return VerifyInSingleDocumentAsync( markup, LanguageNames.VisualBasic, @@ -1215,7 +1329,7 @@ Implements IEnumerable targets: ImmutableArray.Create( new TargetInfo( targetSymbolDisplayName: "Interface IEnumerable", - relationship: InheritanceRelationship.Implementing, + relationship: InheritanceRelationship.ImplementedInterface, inMetadata: true)))); } @@ -1223,13 +1337,13 @@ Implements IEnumerable public Task TestVisualBasicEventStatement() { var markup = @" -Interface {|target2:IBar|} - Event {|target4:e|} As EventHandler -End Interface -Class {|target1:Bar|} - Implements IBar - Public Event {|target3:e|} As EventHandler Implements IBar.e -End Class"; + Interface {|target2:IBar|} + Event {|target4:e|} As EventHandler + End Interface + Class {|target1:Bar|} + Implements IBar + Public Event {|target3:e|} As EventHandler Implements IBar.e + End Class"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, @@ -1237,7 +1351,7 @@ Implements IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 5, @@ -1245,7 +1359,7 @@ Implements IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForEventInInterface = new TestInheritanceMemberItem( lineNumber: 3, @@ -1253,7 +1367,7 @@ Implements IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Event Bar.e As EventHandler", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForEventInClass = new TestInheritanceMemberItem( lineNumber: 7, @@ -1261,7 +1375,7 @@ Implements IBar ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Event IBar.e As EventHandler", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -1276,21 +1390,21 @@ Implements IBar public Task TestVisualBasicEventBlock() { var markup = @" -Interface {|target2:IBar|} - Event {|target4:e|} As EventHandler -End Interface -Class {|target1:Bar|} - Implements IBar - Public Custom Event {|target3:e|} As EventHandler Implements IBar.e - End Event -End Class"; + Interface {|target2:IBar|} + Event {|target4:e|} As EventHandler + End Interface + Class {|target1:Bar|} + Implements IBar + Public Custom Event {|target3:e|} As EventHandler Implements IBar.e + End Event + End Class"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, memberName: "Interface IBar", ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 5, @@ -1298,7 +1412,7 @@ End Event ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForEventInInterface = new TestInheritanceMemberItem( lineNumber: 3, @@ -1306,7 +1420,7 @@ End Event ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Event Bar.e As EventHandler", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForEventInClass = new TestInheritanceMemberItem( lineNumber: 7, @@ -1314,7 +1428,7 @@ End Event ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Event IBar.e As EventHandler", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -1329,31 +1443,31 @@ End Event public Task TestVisualBasicInterfaceMembers() { var markup = @" -Interface {|target2:IBar|} - Property {|target4:Poo|} As Integer - Function {|target6:Foo|}() As Integer -End Interface - -Class {|target1:Bar|} - Implements IBar - Public Property {|target3:Poo|} As Integer Implements IBar.Poo - Get - Return 1 - End Get - Set(value As Integer) - End Set - End Property - Public Function {|target5:Foo|}() As Integer Implements IBar.Foo - Return 1 - End Function -End Class"; + Interface {|target2:IBar|} + Property {|target4:Poo|} As Integer + Function {|target6:Foo|}() As Integer + End Interface + + Class {|target1:Bar|} + Implements IBar + Public Property {|target3:Poo|} As Integer Implements IBar.Poo + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property + Public Function {|target5:Foo|}() As Integer Implements IBar.Foo + Return 1 + End Function + End Class"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, memberName: "Interface IBar", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 7, @@ -1361,7 +1475,7 @@ End Function targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForPooInInterface = new TestInheritanceMemberItem( lineNumber: 3, @@ -1369,7 +1483,7 @@ End Function targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Property Bar.Poo As Integer", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForPooInClass = new TestInheritanceMemberItem( lineNumber: 9, @@ -1377,7 +1491,7 @@ End Function targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Property IBar.Poo As Integer", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); var itemForFooInInterface = new TestInheritanceMemberItem( lineNumber: 4, @@ -1385,7 +1499,7 @@ End Function targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Function Bar.Foo() As Integer", locationTag: "target5", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForFooInClass = new TestInheritanceMemberItem( lineNumber: 16, @@ -1393,7 +1507,7 @@ End Function targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Function IBar.Foo() As Integer", locationTag: "target6", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -1410,22 +1524,22 @@ End Function public Task TestVisualBasicMustInheritClassMember() { var markup = @" -MustInherit Class {|target2:Bar1|} - Public MustOverride Sub {|target4:Foo|}() -End Class - -Class {|target1:Bar|} - Inherits Bar1 - Public Overrides Sub {|target3:Foo|}() - End Sub -End Class"; + MustInherit Class {|target2:Bar1|} + Public MustOverride Sub {|target4:Foo|}() + End Class + + Class {|target1:Bar|} + Inherits Bar1 + Public Overrides Sub {|target3:Foo|}() + End Sub + End Class"; var itemForBar1 = new TestInheritanceMemberItem( lineNumber: 2, memberName: "Class Bar1", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: $"Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.DerivedType))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 6, @@ -1433,7 +1547,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar1", locationTag: "target2", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.BaseType))); var itemForFooInBar1 = new TestInheritanceMemberItem( lineNumber: 3, @@ -1441,7 +1555,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Overrides Sub Bar.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Overridden))); + relationship: InheritanceRelationship.OverridingMember))); var itemForFooInBar = new TestInheritanceMemberItem( lineNumber: 8, @@ -1449,7 +1563,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "MustOverride Sub Bar1.Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Overriding))); + relationship: InheritanceRelationship.OverriddenMember))); return VerifyInSingleDocumentAsync( markup, @@ -1465,49 +1579,49 @@ End Sub public Task TestVisualBasicOverrideMemberCanFindImplementingInterface(bool testDuplicate) { var markup1 = @" -Interface {|target4:IBar|} - Sub {|target6:Foo|}() -End Interface - -Class {|target1:Bar1|} - Implements IBar - Public Overridable Sub {|target2:Foo|}() Implements IBar.Foo - End Sub -End Class - -Class {|target5:Bar2|} - Inherits Bar1 - Public Overrides Sub {|target3:Foo|}() - End Sub -End Class"; + Interface {|target4:IBar|} + Sub {|target6:Foo|}() + End Interface + + Class {|target1:Bar1|} + Implements IBar + Public Overridable Sub {|target2:Foo|}() Implements IBar.Foo + End Sub + End Class + + Class {|target5:Bar2|} + Inherits Bar1 + Public Overrides Sub {|target3:Foo|}() + End Sub + End Class"; var markup2 = @" -Interface {|target4:IBar|} - Sub {|target6:Foo|}() -End Interface - -Class {|target1:Bar1|} - Implements IBar - Public Overridable Sub {|target2:Foo|}() Implements IBar.Foo - End Sub -End Class - -Class {|target5:Bar2|} - Inherits Bar1 - Public Overrides Sub {|target3:Foo|}() - End Sub -End Class"; + Interface {|target4:IBar|} + Sub {|target6:Foo|}() + End Interface + + Class {|target1:Bar1|} + Implements IBar + Public Overridable Sub {|target2:Foo|}() Implements IBar.Foo + End Sub + End Class + + Class {|target5:Bar2|} + Inherits Bar1 + Public Overrides Sub {|target3:Foo|}() + End Sub + End Class"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, memberName: "Interface IBar", targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar1", locationTag: "target1", - relationship: InheritanceRelationship.Implemented), + relationship: InheritanceRelationship.ImplementingType), new TargetInfo( targetSymbolDisplayName: "Class Bar2", locationTag: "target5", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInIBar = new TestInheritanceMemberItem( lineNumber: 3, @@ -1515,11 +1629,11 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Overridable Sub Bar1.Foo()", locationTag: "target2", - relationship: InheritanceRelationship.Implemented), + relationship: InheritanceRelationship.ImplementingMember), new TargetInfo( targetSymbolDisplayName: "Overrides Sub Bar2.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForBar1 = new TestInheritanceMemberItem( lineNumber: 6, @@ -1527,11 +1641,11 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target4", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.ImplementedInterface), new TargetInfo( targetSymbolDisplayName: "Class Bar2", locationTag: "target5", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.DerivedType))); var itemForFooInBar1 = new TestInheritanceMemberItem( lineNumber: 8, @@ -1539,11 +1653,11 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub IBar.Foo()", locationTag: "target6", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.ImplementedMember), new TargetInfo( targetSymbolDisplayName: "Overrides Sub Bar2.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Overridden))); + relationship: InheritanceRelationship.OverridingMember))); var itemForBar2 = new TestInheritanceMemberItem( lineNumber: 12, @@ -1552,11 +1666,11 @@ End Sub new TargetInfo( targetSymbolDisplayName: "Class Bar1", locationTag: "target1", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.BaseType), new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target4", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForFooInBar2 = new TestInheritanceMemberItem( lineNumber: 14, @@ -1564,11 +1678,11 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub IBar.Foo()", locationTag: "target6", - relationship: InheritanceRelationship.Implementing), + relationship: InheritanceRelationship.ImplementedMember), new TargetInfo( targetSymbolDisplayName: "Overridable Sub Bar1.Foo()", locationTag: "target2", - relationship: InheritanceRelationship.Overriding))); + relationship: InheritanceRelationship.OverriddenMember))); return VerifyInSingleDocumentAsync( testDuplicate ? markup2 : markup1, @@ -1585,22 +1699,22 @@ End Sub public Task TestVisualBasicFindGenericsBaseType() { var markup = @" -Public Interface {|target5:IBar|}(Of T) - Sub {|target6:Foo|}() -End Interface + Public Interface {|target5:IBar|}(Of T) + Sub {|target6:Foo|}() + End Interface -Public Class {|target1:Bar|} - Implements IBar(Of Integer) - Implements IBar(Of String) + Public Class {|target1:Bar|} + Implements IBar(Of Integer) + Implements IBar(Of String) - Public Sub {|target3:Foo|}() Implements IBar(Of Integer).Foo - Throw New NotImplementedException() - End Sub + Public Sub {|target3:Foo|}() Implements IBar(Of Integer).Foo + Throw New NotImplementedException() + End Sub - Private Sub {|target4:IBar_Foo|}() Implements IBar(Of String).Foo - Throw New NotImplementedException() - End Sub -End Class"; + Private Sub {|target4:IBar_Foo|}() Implements IBar(Of String).Foo + Throw New NotImplementedException() + End Sub + End Class"; var itemForIBar = new TestInheritanceMemberItem( lineNumber: 2, @@ -1608,7 +1722,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar", locationTag: "target1", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInIBar = new TestInheritanceMemberItem( lineNumber: 3, @@ -1616,11 +1730,11 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub Bar.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implemented), + relationship: InheritanceRelationship.ImplementingMember), new TargetInfo( targetSymbolDisplayName: "Sub Bar.IBar_Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); var itemForBar = new TestInheritanceMemberItem( lineNumber: 6, @@ -1628,7 +1742,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar(Of T)", locationTag: "target5", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForFooInBar = new TestInheritanceMemberItem( lineNumber: 10, @@ -1636,7 +1750,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub IBar(Of T).Foo()", locationTag: "target6", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); var itemForIBar_FooInBar = new TestInheritanceMemberItem( lineNumber: 14, @@ -1644,7 +1758,7 @@ End Sub targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub IBar(Of T).Foo()", locationTag: "target6", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); return VerifyInSingleDocumentAsync( markup, @@ -1662,21 +1776,21 @@ End Sub public Task TestCSharpProjectReferencingVisualBasicProject() { var markup1 = @" -using MyNamespace; -namespace BarNs -{ - public class {|target2:Bar|} : IBar - { - public void {|target4:Foo|}() { } - } -}"; + using MyNamespace; + namespace BarNs + { + public class {|target2:Bar|} : IBar + { + public void {|target4:Foo|}() { } + } + }"; var markup2 = @" -Namespace MyNamespace - Public Interface {|target1:IBar|} - Sub {|target3:Foo|}() - End Interface -End Namespace"; + Namespace MyNamespace + Public Interface {|target1:IBar|} + Sub {|target3:Foo|}() + End Interface + End Namespace"; var itemForBar = new TestInheritanceMemberItem( lineNumber: 5, @@ -1684,7 +1798,7 @@ End Interface targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Interface IBar", locationTag: "target1", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForFooInMarkup1 = new TestInheritanceMemberItem( lineNumber: 7, @@ -1692,7 +1806,7 @@ End Interface targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub IBar.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); var itemForIBar = new TestInheritanceMemberItem( lineNumber: 3, @@ -1700,7 +1814,7 @@ End Interface targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "class Bar", locationTag: "target2", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInMarkup2 = new TestInheritanceMemberItem( lineNumber: 4, @@ -1708,7 +1822,7 @@ End Interface targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void Bar.Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); return VerifyInDifferentProjectsAsync( (markup1, LanguageNames.CSharp), @@ -1721,24 +1835,24 @@ End Interface public Task TestVisualBasicProjectReferencingCSharpProject() { var markup1 = @" -Imports BarNs -Namespace MyNamespace - Public Class {|target2:Bar44|} - Implements IBar + Imports BarNs + Namespace MyNamespace + Public Class {|target2:Bar44|} + Implements IBar - Public Sub {|target4:Foo|}() Implements IBar.Foo - End Sub - End Class -End Namespace"; + Public Sub {|target4:Foo|}() Implements IBar.Foo + End Sub + End Class + End Namespace"; var markup2 = @" -namespace BarNs -{ - public interface {|target1:IBar|} - { - void {|target3:Foo|}(); - } -}"; + namespace BarNs + { + public interface {|target1:IBar|} + { + void {|target3:Foo|}(); + } + }"; var itemForBar44 = new TestInheritanceMemberItem( lineNumber: 4, @@ -1746,7 +1860,7 @@ public interface {|target1:IBar|} targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "interface IBar", locationTag: "target1", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedInterface))); var itemForFooInMarkup1 = new TestInheritanceMemberItem( lineNumber: 7, @@ -1754,7 +1868,7 @@ public interface {|target1:IBar|} targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "void IBar.Foo()", locationTag: "target3", - relationship: InheritanceRelationship.Implementing))); + relationship: InheritanceRelationship.ImplementedMember))); var itemForIBar = new TestInheritanceMemberItem( lineNumber: 4, @@ -1762,7 +1876,7 @@ public interface {|target1:IBar|} targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Class Bar44", locationTag: "target2", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingType))); var itemForFooInMarkup2 = new TestInheritanceMemberItem( lineNumber: 6, @@ -1770,7 +1884,7 @@ public interface {|target1:IBar|} targets: ImmutableArray.Create(new TargetInfo( targetSymbolDisplayName: "Sub Bar44.Foo()", locationTag: "target4", - relationship: InheritanceRelationship.Implemented))); + relationship: InheritanceRelationship.ImplementingMember))); return VerifyInDifferentProjectsAsync( (markup1, LanguageNames.VisualBasic), diff --git a/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs b/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs index cb59f140b4de9..fc2368f442f1f 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs @@ -3087,5 +3087,33 @@ public class TestType var metadataAsSourceFile = await context.GenerateSourceAsync(navigationSymbol); TestContext.VerifyResult(metadataAsSourceFile, expected); } + + [WorkItem(22431, "https://github.com/dotnet/roslyn/issues/22431")] + [Fact, Trait(Traits.Feature, Traits.Features.MetadataAsSource)] + public async Task TestCDATAComment() + { + var source = @" +public enum BinaryOperatorKind +{ + /// + /// Represents the operator. + /// + LeftShift = 0x8, +} +"; + var symbolName = "BinaryOperatorKind.LeftShift"; + var expectedCS = $@"#region {FeaturesResources.Assembly} ReferencedAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null +// {CodeAnalysisResources.InMemoryAssembly} +#endregion + +public enum BinaryOperatorKind +{{ + // + // {FeaturesResources.Summary_colon} + // Represents the '<<' operator. + [|LeftShift|] = 8 +}}"; + await GenerateAndVerifySourceAsync(source, symbolName, LanguageNames.CSharp, expectedCS, includeXmlDocComments: true); + } } } diff --git a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs index 14eb9c357849b..3ecd69d10d544 100644 --- a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs +++ b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs @@ -788,14 +788,23 @@ internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, var expectedDocumentSyntaxEvents = 1; var expectedDocumentSemanticEvents = 5; + var listenerProvider = GetListenerProvider(workspace.ExportProvider); + + // start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test + var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation"); + var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync(); + workspace.ChangeDocument(document.Id, SourceText.From("//")); if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0) { analyzer.RunningEvent.Wait(); } + token.Dispose(); + workspace.ChangeDocument(document.Id, SourceText.From("// ")); await WaitAsync(service, workspace); + await expeditedWait; service.Unregister(workspace); @@ -833,6 +842,12 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope service.Register(workspace); + var listenerProvider = GetListenerProvider(workspace.ExportProvider); + + // start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test + var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation"); + var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync(); + workspace.ChangeDocument(document.Id, SourceText.From("//")); if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0) { @@ -846,8 +861,11 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope analyzer.RunningEvent.Wait(); } + token.Dispose(); + workspace.ChangeDocument(document.Id, SourceText.From("// ")); await WaitAsync(service, workspace); + await expeditedWait; service.Unregister(workspace); @@ -1273,6 +1291,12 @@ public async Task FileFromSameProjectTogetherTest() await WaitWaiterAsync(workspace.ExportProvider); + var listenerProvider = GetListenerProvider(workspace.ExportProvider); + + // start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test + var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation"); + var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync(); + // we want to test order items processed by solution crawler. // but since everything async, lazy and cancellable, order is not 100% deterministic. an item might // start to be processed, and get cancelled due to newly enqueued item requiring current work to be re-processed @@ -1319,8 +1343,11 @@ await crawlerListener.WaitUntilConditionIsMetAsync( operation.Done(); } + token.Dispose(); + // wait analyzers to finish process await WaitAsync(service, workspace); + await expeditedWait; Assert.Equal(1, worker.DocumentIds.Take(5).Select(d => d.ProjectId).Distinct().Count()); Assert.Equal(1, worker.DocumentIds.Skip(5).Take(5).Select(d => d.ProjectId).Distinct().Count()); @@ -1339,7 +1366,6 @@ private static async Task InsertText(string code, string text, bool expectDocume composition: EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(IIncrementalAnalyzerProvider)).AddParts(typeof(AnalyzerProviderNoWaitNoBlock)), workspaceKind: SolutionCrawlerWorkspaceKind); - SetOptions(workspace); var testDocument = workspace.Documents.First(); var textBuffer = testDocument.GetTextBuffer(); @@ -1476,18 +1502,6 @@ private static IEnumerable GetDocuments(ProjectId projectId, int c private static AsynchronousOperationListenerProvider GetListenerProvider(ExportProvider provider) => provider.GetExportedValue(); - private static void SetOptions(Workspace workspace) - { - // override default timespan to make test run faster - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options - .WithChangedOption(InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS, 100))); - } - private static void MakeFirstDocumentActive(Project project) => MakeDocumentActive(project.Documents.First()); @@ -1518,8 +1532,6 @@ public WorkCoordinatorWorkspace(string workspaceKind = null, bool disablePartial Assert.False(_workspaceWaiter.HasPendingWork); Assert.False(_solutionCrawlerWaiter.HasPendingWork); - - WorkCoordinatorTests.SetOptions(this); } public static WorkCoordinatorWorkspace CreateWithAnalysisScope(BackgroundAnalysisScope analysisScope, string workspaceKind = null, bool disablePartialSolutions = true, Type incrementalAnalyzer = null) diff --git a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs index a1e78c1c09379..f3fa70ebc6c54 100644 --- a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs +++ b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Structure; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Text.Adornments; using Microsoft.VisualStudio.Text.Tagging; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -25,7 +26,10 @@ public class StructureTaggerTests { [WpfTheory, Trait(Traits.Feature, Traits.Features.Outlining)] [CombinatorialData] - public async Task CSharpOutliningTagger(bool collapseRegionsWhenCollapsingToDefinitions) + public async Task CSharpOutliningTagger( + bool collapseRegionsWhenCollapsingToDefinitions, + bool showBlockStructureGuidesForDeclarationLevelConstructs, + bool showBlockStructureGuidesForCodeLevelConstructs) { var code = @"using System; @@ -36,6 +40,11 @@ public class MyClass { static void Main(string[] args) { + if (false) + { + return; + } + int x = 5; } } @@ -44,7 +53,9 @@ static void Main(string[] args) using var workspace = TestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options - .WithChangedOption(BlockStructureOptions.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions))); + .WithChangedOption(BlockStructureOptions.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions) + .WithChangedOption(BlockStructureOptions.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs) + .WithChangedOption(BlockStructureOptions.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs))); var tags = await GetTagsFromWorkspaceAsync(workspace); @@ -52,37 +63,56 @@ static void Main(string[] args) namespaceTag => { Assert.False(namespaceTag.IsImplementation); - Assert.Equal(12, GetCollapsedHintLineCount(namespaceTag)); + Assert.Equal(17, GetCollapsedHintLineCount(namespaceTag)); + Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Namespace : PredefinedStructureTagTypes.Nonstructural, namespaceTag.Type); Assert.Equal("namespace MyNamespace", GetHeaderText(namespaceTag)); }, regionTag => { Assert.Equal(collapseRegionsWhenCollapsingToDefinitions, regionTag.IsImplementation); - Assert.Equal(9, GetCollapsedHintLineCount(regionTag)); + Assert.Equal(14, GetCollapsedHintLineCount(regionTag)); + Assert.Equal(PredefinedStructureTagTypes.Nonstructural, regionTag.Type); Assert.Equal("#region MyRegion", GetHeaderText(regionTag)); }, classTag => { Assert.False(classTag.IsImplementation); - Assert.Equal(7, GetCollapsedHintLineCount(classTag)); + Assert.Equal(12, GetCollapsedHintLineCount(classTag)); + Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, classTag.Type); Assert.Equal("public class MyClass", GetHeaderText(classTag)); }, methodTag => { Assert.True(methodTag.IsImplementation); - Assert.Equal(4, GetCollapsedHintLineCount(methodTag)); + Assert.Equal(9, GetCollapsedHintLineCount(methodTag)); + Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Member : PredefinedStructureTagTypes.Nonstructural, methodTag.Type); Assert.Equal("static void Main(string[] args)", GetHeaderText(methodTag)); + }, + ifTag => + { + Assert.False(ifTag.IsImplementation); + Assert.Equal(4, GetCollapsedHintLineCount(ifTag)); + Assert.Equal(showBlockStructureGuidesForCodeLevelConstructs ? PredefinedStructureTagTypes.Conditional : PredefinedStructureTagTypes.Nonstructural, ifTag.Type); + Assert.Equal("if (false)", GetHeaderText(ifTag)); }); } - [WpfFact, Trait(Traits.Feature, Traits.Features.Outlining)] - public async Task VisualBasicOutliningTagger() + [WpfTheory, Trait(Traits.Feature, Traits.Features.Outlining)] + [CombinatorialData] + public async Task VisualBasicOutliningTagger( + bool collapseRegionsWhenCollapsingToDefinitions, + bool showBlockStructureGuidesForDeclarationLevelConstructs, + bool showBlockStructureGuidesForCodeLevelConstructs) { var code = @"Imports System Namespace MyNamespace #Region ""MyRegion"" Module M Sub Main(args As String()) + If False Then + Return + End If + Dim x As Integer = 5 End Sub End Module @@ -90,32 +120,48 @@ End Module End Namespace"; using var workspace = TestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf); + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options + .WithChangedOption(BlockStructureOptions.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.VisualBasic, collapseRegionsWhenCollapsingToDefinitions) + .WithChangedOption(BlockStructureOptions.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.VisualBasic, showBlockStructureGuidesForDeclarationLevelConstructs) + .WithChangedOption(BlockStructureOptions.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.VisualBasic, showBlockStructureGuidesForCodeLevelConstructs))); + var tags = await GetTagsFromWorkspaceAsync(workspace); Assert.Collection(tags, namespaceTag => { Assert.False(namespaceTag.IsImplementation); - Assert.Equal(9, GetCollapsedHintLineCount(namespaceTag)); + Assert.Equal(13, GetCollapsedHintLineCount(namespaceTag)); + Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Namespace : PredefinedStructureTagTypes.Nonstructural, namespaceTag.Type); Assert.Equal("Namespace MyNamespace", GetHeaderText(namespaceTag)); }, regionTag => { - Assert.False(regionTag.IsImplementation); - Assert.Equal(7, GetCollapsedHintLineCount(regionTag)); + Assert.Equal(collapseRegionsWhenCollapsingToDefinitions, regionTag.IsImplementation); + Assert.Equal(11, GetCollapsedHintLineCount(regionTag)); + Assert.Equal(PredefinedStructureTagTypes.Nonstructural, regionTag.Type); Assert.Equal(@"#Region ""MyRegion""", GetHeaderText(regionTag)); }, moduleTag => { Assert.False(moduleTag.IsImplementation); - Assert.Equal(5, GetCollapsedHintLineCount(moduleTag)); + Assert.Equal(9, GetCollapsedHintLineCount(moduleTag)); + Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, moduleTag.Type); Assert.Equal("Module M", GetHeaderText(moduleTag)); }, methodTag => { Assert.True(methodTag.IsImplementation); - Assert.Equal(3, GetCollapsedHintLineCount(methodTag)); + Assert.Equal(7, GetCollapsedHintLineCount(methodTag)); + Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Member : PredefinedStructureTagTypes.Nonstructural, methodTag.Type); Assert.Equal("Sub Main(args As String())", GetHeaderText(methodTag)); + }, + ifTag => + { + Assert.False(ifTag.IsImplementation); + Assert.Equal(3, GetCollapsedHintLineCount(ifTag)); + Assert.Equal(showBlockStructureGuidesForCodeLevelConstructs ? PredefinedStructureTagTypes.Conditional : PredefinedStructureTagTypes.Nonstructural, ifTag.Type); + Assert.Equal("If False Then", GetHeaderText(ifTag)); }); } diff --git a/src/EditorFeatures/Test/UnusedReferences/UnusedReferencesRemoverTests.cs b/src/EditorFeatures/Test/UnusedReferences/UnusedReferencesRemoverTests.cs index 3e1b569e0b6ee..b7cadd79d5097 100644 --- a/src/EditorFeatures/Test/UnusedReferences/UnusedReferencesRemoverTests.cs +++ b/src/EditorFeatures/Test/UnusedReferences/UnusedReferencesRemoverTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; @@ -15,16 +16,42 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.UnusedReferences { public class UnusedReferencesRemoverTests { - private const string UsedAssemblyPath = "/libs/Used.dll"; - private const string UnusedAssemblyPath = "/libs/Unused.dll"; + private static readonly string[] Empty = Array.Empty(); + + private const string UsedAssemblyName = "Used.dll"; + private const string UsedAssemblyPath = $"/libs/{UsedAssemblyName}"; + private const string UnusedAssemblyName = "Unused.dll"; + private const string UnusedAssemblyPath = $"/libs/{UnusedAssemblyName}"; [Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)] - public void GetUnusedReferences_UsedReferences_AreNotReturned() + public void GetUnusedReferences_DirectlyUsedAssemblyReferences_AreNotReturned() { var usedAssemblies = new[] { UsedAssemblyPath }; var usedReference = AssemblyReference(UsedAssemblyPath); - var unusedReferences = GetUnusedReferences(usedAssemblies, usedReference); + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, usedReference); + + Assert.Empty(unusedReferences); + } + + [Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)] + public void GetUnusedReferences_DirectlyUsedPackageReferences_AreNotReturned() + { + var usedAssemblies = new[] { UsedAssemblyPath }; + var usedReference = PackageReference(UsedAssemblyPath); + + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, usedReference); + + Assert.Empty(unusedReferences); + } + + [Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)] + public void GetUnusedReferences_DirectlyUsedProjectReferences_AreNotReturned() + { + var usedProjects = new[] { UsedAssemblyName }; + var usedReference = ProjectReference(UsedAssemblyName); + + var unusedReferences = GetUnusedReferences(usedCompilationAssemblies: Empty, usedProjects, usedReference); Assert.Empty(unusedReferences); } @@ -35,19 +62,30 @@ public void GetUnusedReferences_UnusedReferences_AreReturned() var usedAssemblies = new[] { UsedAssemblyPath }; var unusedReference = PackageReference(UnusedAssemblyPath); - var unusedReferences = GetUnusedReferences(usedAssemblies, unusedReference); + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, unusedReference); Assert.Contains(unusedReference, unusedReferences); Assert.Single(unusedReferences); } [Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)] - public void GetUnusedReferences_TransitivelyUsedReferences_AreNotReturned() + public void GetUnusedReferences_TransitivelyUsedPackageReferences_AreNotReturned() { var usedAssemblies = new[] { UsedAssemblyPath }; - var transitivelyUsedReference = ProjectReference(UnusedAssemblyPath, PackageReference(UsedAssemblyPath)); + var transitivelyUsedReference = PackageReference(UnusedAssemblyName, PackageReference(UsedAssemblyPath)); + + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, transitivelyUsedReference); + + Assert.Empty(unusedReferences); + } + + [Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)] + public void GetUnusedReferences_TransitivelyUsedProjectReferences_AreNotReturned() + { + var usedAssemblies = new[] { UsedAssemblyPath }; + var transitivelyUsedReference = ProjectReference(UnusedAssemblyName, PackageReference(UsedAssemblyPath)); - var unusedReferences = GetUnusedReferences(usedAssemblies, transitivelyUsedReference); + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, transitivelyUsedReference); Assert.Empty(unusedReferences); } @@ -59,7 +97,7 @@ public void GetUnusedReferences_WhenUsedAssemblyIsAvilableDirectlyAndTransitivel var transitivelyUsedReference = ProjectReference(UnusedAssemblyPath, PackageReference(UsedAssemblyPath)); var directlyUsedReference = PackageReference(UsedAssemblyPath); - var unusedReferences = GetUnusedReferences(usedAssemblies, transitivelyUsedReference, directlyUsedReference); + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, transitivelyUsedReference, directlyUsedReference); Assert.Contains(transitivelyUsedReference, unusedReferences); Assert.Single(unusedReferences); @@ -76,7 +114,7 @@ public void GetUnusedReferences_ReferencesThatDoNotContributeToCompilation_AreNo compilationAssemblies: ImmutableArray.Empty, dependencies: ImmutableArray.Empty); - var unusedReferences = GetUnusedReferences(usedAssemblies, analyzerReference); + var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, analyzerReference); Assert.Empty(unusedReferences); } @@ -122,8 +160,8 @@ public async Task ApplyReferenceUpdates_MixOfChangeAndNoChangeUpdates_ChangesAre Assert.Single(appliedUpdates); } - private static ImmutableArray GetUnusedReferences(string[] usedCompilationAssemblies, params ReferenceInfo[] references) - => UnusedReferencesRemover.GetUnusedReferences(new(usedCompilationAssemblies), references.ToImmutableArray()); + private static ImmutableArray GetUnusedReferences(string[] usedCompilationAssemblies, string[] usedProjectAssemblyNames, params ReferenceInfo[] references) + => UnusedReferencesRemover.GetUnusedReferences(new(usedCompilationAssemblies), new(usedProjectAssemblyNames), references.ToImmutableArray()); private static async Task> ApplyReferenceUpdatesAsync(params ReferenceUpdate[] referenceUpdates) { diff --git a/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs b/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs new file mode 100644 index 0000000000000..549ff737d21ef --- /dev/null +++ b/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ValueTracking; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using System.Threading; +using Microsoft.CodeAnalysis.Text; +using Xunit; +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.ValueTracking +{ + public abstract class AbstractBaseValueTrackingTests + { + protected TestWorkspace CreateWorkspace(string code, TestHost testHost) + => CreateWorkspace(code, EditorTestCompositions.EditorFeatures.WithTestHostParts(testHost)); + + protected abstract TestWorkspace CreateWorkspace(string code, TestComposition composition); + + internal static async Task> GetTrackedItemsAsync(TestWorkspace testWorkspace, CancellationToken cancellationToken = default) + { + var cursorDocument = testWorkspace.DocumentWithCursor; + var document = testWorkspace.CurrentSolution.GetRequiredDocument(cursorDocument.Id); + var textSpan = new TextSpan(cursorDocument.CursorPosition!.Value, 0); + var service = testWorkspace.Services.GetRequiredService(); + return await service.TrackValueSourceAsync(textSpan, document, cancellationToken); + + } + + internal static async Task> GetTrackedItemsAsync(TestWorkspace testWorkspace, ValueTrackedItem item, CancellationToken cancellationToken = default) + { + var service = testWorkspace.Services.GetRequiredService(); + return await service.TrackValueSourceAsync(testWorkspace.CurrentSolution, item, cancellationToken); + } + + internal static async Task> ValidateItemsAsync(TestWorkspace testWorkspace, (int line, string text)[] itemInfo, CancellationToken cancellationToken = default) + { + var items = await GetTrackedItemsAsync(testWorkspace, cancellationToken); + Assert.True(itemInfo.Length == items.Length, $"GetTrackedItemsAsync\n\texpected: [{string.Join(",", itemInfo.Select(p => p.text))}]\n\t actual: [{string.Join(",", items)}]"); + + for (var i = 0; i < items.Length; i++) + { + ValidateItem(items[i], itemInfo[i].line, itemInfo[i].text); + } + + return items; + } + + internal static async Task> ValidateChildrenAsync(TestWorkspace testWorkspace, ValueTrackedItem item, (int line, string text)[] childInfo, CancellationToken cancellationToken = default) + { + var children = await GetTrackedItemsAsync(testWorkspace, item, cancellationToken); + Assert.True(childInfo.Length == children.Length, $"GetTrackedItemsAsync on [{item}]\n\texpected: [{string.Join(",", childInfo.Select(p => p.text))}]\n\t actual: [{string.Join(",", children)}]"); + + for (var i = 0; i < childInfo.Length; i++) + { + ValidateItem(children[i], childInfo[i].line, childInfo[i].text); + } + + return children; + } + + internal static async Task ValidateChildrenEmptyAsync(TestWorkspace testWorkspace, ValueTrackedItem item, CancellationToken cancellationToken = default) + { + var children = await GetTrackedItemsAsync(testWorkspace, item, cancellationToken); + Assert.Empty(children); + } + + internal static async Task ValidateChildrenEmptyAsync(TestWorkspace testWorkspace, IEnumerable items, CancellationToken cancellationToken = default) + { + foreach (var item in items) + { + await ValidateChildrenEmptyAsync(testWorkspace, item, cancellationToken); + } + } + + internal static void ValidateItem(ValueTrackedItem item, int line, string? text = null) + { + item.SourceText.GetLineAndOffset(item.Span.Start, out var lineStart, out var _); + Assert.Equal(line, lineStart); + + if (text is not null) + { + Assert.Equal(text, item.ToString()); + } + } + } +} diff --git a/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs b/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs new file mode 100644 index 0000000000000..a738d4c001c52 --- /dev/null +++ b/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs @@ -0,0 +1,963 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.ValueTracking +{ + [UseExportProvider] + public class CSharpValueTrackingTests : AbstractBaseValueTrackingTests + { + protected override TestWorkspace CreateWorkspace(string code, TestComposition composition) + => TestWorkspace.CreateCSharp(code, composition: composition); + + [Theory] + [CombinatorialData] + public async Task TestProperty(TestHost testHost) + { + var code = +@" +class C +{ + public string $$S { get; set; } = """"""; + + public void SetS(string s) + { + S = s; + } + + public string GetS() => S; +} +"; + using var workspace = CreateWorkspace(code, testHost); + + // + // property S + // |> S = s [Code.cs:7] + // |> public string S { get; set; } [Code.cs:3] + // + await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (7, "s"), + (3, "public string S { get; set; } = \"\""), + }); + } + + [Theory] + [CombinatorialData] + public async Task TestPropertyWithThis(TestHost testHost) + { + var code = +@" +class C +{ + public string $$S { get; set; } = """"""; + + public void SetS(string s) + { + this.S = s; + } + + public string GetS() => this.S; +} +"; + using var workspace = CreateWorkspace(code, testHost); + + // + // property S + // |> S = s [Code.cs:7] + // |> public string S { get; set; } [Code.cs:3] + // + await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (7, "s"), + (3, "public string S { get; set; } = \"\""), + }); + } + + [Theory] + [CombinatorialData] + public async Task TestField(TestHost testHost) + { + var code = +@" +class C +{ + private string $$_s = """"""; + + public void SetS(string s) + { + _s = s; + } + + public string GetS() => _s; +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // field _s + // |> _s = s [Code.cs:7] + // |> string _s = "" [Code.cs:3] + // + await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (7, "s"), + (3, "_s = \"\"") + }); + } + + [Theory] + [CombinatorialData] + public async Task TestFieldWithThis(TestHost testHost) + { + var code = +@" +class C +{ + private string $$_s = """"""; + + public void SetS(string s) + { + this._s = s; + } + + public string GetS() => this._s; +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // field _s + // |> this._s = s [Code.cs:7] + // |> string _s = "" [Code.cs:3] + // + await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (7, "s"), + (3, "_s = \"\"") + }); + } + + [Theory] + [CombinatorialData] + public async Task TestLocal(TestHost testHost) + { + var code = +@" +class C +{ + public int Add(int x, int y) + { + var $$z = x; + z += y; + return z; + } +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // local variable z + // |> z += y [Code.cs:6] + // |> var z = x [Code.cs:5] + // + Assert.Equal(2, initialItems.Length); + ValidateItem(initialItems[0], 6); + ValidateItem(initialItems[1], 5); + } + + [Theory] + [CombinatorialData] + public async Task TestParameter(TestHost testHost) + { + var code = +@" +class C +{ + public int Add(int $$x, int y) + { + x += y; + return x; + } +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // parameter x + // |> x += y [Code.cs:5] + // |> Add(int x, int y) [Code.cs:3] + // + Assert.Equal(2, initialItems.Length); + ValidateItem(initialItems[0], 5); + ValidateItem(initialItems[1], 3); + } + + [Theory] + [CombinatorialData] + public async Task TestMissingOnMethod(TestHost testHost) + { + var code = +@" +class C +{ + public int $$Add(int x, int y) + { + x += y; + return x; + } +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + Assert.Empty(initialItems); + } + + [Theory] + [CombinatorialData] + public async Task TestMissingOnClass(TestHost testHost) + { + var code = +@" +class $$C +{ + public int Add(int x, int y) + { + x += y; + return x; + } +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + Assert.Empty(initialItems); + } + + [Theory] + [CombinatorialData] + public async Task TestMissingOnNamespace(TestHost testHost) + { + var code = +@" +namespace $$N +{ + class C + { + public int Add(int x, int y) + { + x += y; + return x; + } + } +}"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + Assert.Empty(initialItems); + } + + [Theory] + [CombinatorialData] + public async Task MethodTracking1(TestHost testHost) + { + var code = +@" +class C +{ + public string S { get; set; } = """"""; + + public void SetS(string s) + { + S$$ = s; + } + + public string GetS() => S; +} + +class Other +{ + public void CallS(C c, string str) + { + c.SetS(str); + } + + public void CallS(C c) + { + CallS(c, CalculateDefault(c)); + } + + private string CalculateDefault(C c) + { + if (c is null) + { + return ""null""; + } + + if (string.IsNullOrEmpty(c.S)) + { + return ""defaultstring""; + } + + return """"; + } +} +"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // S = s; [Code.cs:7] + // |> S = [|s|] [Code.cs:7] + // |> [|c.SetS(str)|]; [Code.cs:17] + // |> c.SetS([|str|]); [Code.cs:17] + // |> CallS([|c|], CalculateDefault(c)) [Code.cs:22] + // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22] + // |> return "" [Code.cs:37] + // |> return "defaultstring" [Code.cs:34] + // |> return "null" [Code.cs:29] + // + Assert.Equal(1, initialItems.Length); + ValidateItem(initialItems[0], 7); + + var items = await ValidateChildrenAsync( + workspace, + initialItems.Single(), + childInfo: new[] + { + (17, "str"), // |> c.SetS([|str|]); [Code.cs:17] + (17, "c.SetS(str)"), // |> [|c.SetS(str)|]; [Code.cs:17] + }); + + // |> [|c.SetS(s)|]; [Code.cs:17] + await ValidateChildrenEmptyAsync(workspace, items[1]); + + // |> c.SetS([|s|]); [Code.cs:17] + items = await ValidateChildrenAsync( + workspace, + items[0], + childInfo: new[] + { + (22, "c" ), // |> CallS([|c|], CalculateDefault(c)) [Code.cs:22] + (22, "c" ), // |> CallS(c, CalculateDefault([|c|])) [Code.cs:22] + (22, "CalculateDefault(c)" ), // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22] + (22, "CallS(c, CalculateDefault(c))" ) // |> [|CallS(c, CalculateDefault(c))|] [Code.cs:22] + }); + + // |> CallS([|c|], CalculateDefault(c)) [Code.cs:22] + await ValidateChildrenEmptyAsync(workspace, items[0]); + // |> CallS(c, CalculateDefault([|c|])) [Code.cs:22] + await ValidateChildrenEmptyAsync(workspace, items[1]); + // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22] + await ValidateChildrenEmptyAsync(workspace, items[3]); + + // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22] + var children = await ValidateChildrenAsync( + workspace, + items[2], + childInfo: new[] + { + (37, "\"\""), // |> return "" [Code.cs:37] + (34, "\"defaultstring\""), // |> return "defaultstring" [Code.cs:34] + (29, "\"null\""), // |> return "null" [Code.cs:29] + }); + + foreach (var child in children) + { + await ValidateChildrenEmptyAsync(workspace, child); + } + } + + [Theory] + [CombinatorialData] + public async Task MethodTracking2(TestHost testHost) + { + var code = +@" +class C +{ + public string S { get; set; } = """"""; + + public void SetS(string s) + { + S$$ = s; + } + + public string GetS() => S; +} + +class Other +{ + private readonly string _adornment; + public Other(string adornment) + { + _adornment = adornment; + } + + public void CallS(C c, string s) + { + c.SetS(s); + } + + public void CallS(C c) + { + CallS(c, CalculateDefault(c) + _adornment); + } + + private string CalculateDefault(C c) + { + if (c is null) + { + return ""null""; + } + + if (string.IsNullOrEmpty(c.S)) + { + return ""defaultstring""; + } + + return """"; + } +} + +class Program +{ + public static void Main(string[] args) + { + var other = new Other(""some value""); + var c = new C(); + other.CallS(c); + } +} +"; + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // S = s; [Code.cs:7] + // |> S = [|s|] [Code.cs:7] + // |> [|c.SetS(s)|]; [Code.cs:23] + // |> c.SetS([|s|]); [Code.cs:23] + // |> CallS([|c|], CalculateDefault(c) + _adornment) [Code.cs:28] + // |> other.CallS([|c|]); [Code.cs:53] + // |> CallS(c, CalculateDefault(c) + [|_adornment|]) [Code.cs:28] + // |> _adornment = [|adornment|]; [Code.cs:18] + // |> var other = new Other([|"some value"|]); [Code.cs:51] + // |> CallS(c, CalculateDefault([|c|]) + _adornment) [Code.cs:28] + // |> other.CallS([|c|]); [Code.cs:53] + // |> CallS(c, [|CalculateDefault(c)|] + _adornment) [Code.cs:28] + // |> return "" [Code.cs:37] + // |> return "defaultstring" [Code.cs:34] + // |> return "null" [Code.cs:29] + // |> [|CallS(c, CalculateDefault(c) + _adornment)|] [Code.cs:28] + // + Assert.Equal(1, initialItems.Length); + ValidateItem(initialItems[0], 7); + + var items = await ValidateChildrenAsync( + workspace, + initialItems.Single(), + childInfo: new[] + { + (23, "s"), // |> c.SetS([|s|]); [Code.cs:23] + (23, "c.SetS(s)"), // |> c.SetS(s); [Code.cs:23] + }); + + // |> c.SetS(s); [Code.cs:23] + await ValidateChildrenEmptyAsync(workspace, items[1]); + + // |> c.SetS([|s|]); [Code.cs:23] + items = await ValidateChildrenAsync( + workspace, + items[0], + childInfo: new[] + { + (28, "c" ), // |> CallS([|c|], CalculateDefault(c) + _adornment) [Code.cs:28] + (28, "_adornment" ), // |> CallS(c, CalculateDefault(c) + [|_adornment|]) [Code.cs:28] + (28, "c" ), // |> CallS(c, CalculateDefault([|c|]) + _adornment) [Code.cs:28] + (28, "CalculateDefault(c)" ), // |> CallS(c, [|CalculateDefault|](c) + _adornment) [Code.cs:28] + (28, "CallS(c, CalculateDefault(c) + _adornment)" ), // |> [|CallS(c, CalculateDefault(c) + _adornment)|] [Code.cs:28] + }); + + // |> CallS([|c|], CalculateDefault(c) + _adornment) [Code.cs:28] + var children = await ValidateChildrenAsync( + workspace, + items[0], + childInfo: new[] + { + (53, "other.CallS(c)"), // |> other.CallS([|c|]); [Code.cs:53] + }); + + await ValidateChildrenEmptyAsync(workspace, children); + + // |> CallS(c, CalculateDefault([|c|]) + _adornment) [Code.cs:28] + children = await ValidateChildrenAsync( + workspace, + items[2], + childInfo: new[] + { + (53, "other.CallS(c)"), // |> other.CallS([|c|]); [Code.cs:53] + }); + + await ValidateChildrenEmptyAsync(workspace, children); + + // |> CallS(c, CalculateDefault(c) + [|_adornment|]) [Code.cs:28] + children = await ValidateChildrenAsync( + workspace, + items[1], + childInfo: new[] + { + (18, "adornment"), // |> _adornment = [|adornment|] [Code.cs:18] + }); + + children = await ValidateChildrenAsync( + workspace, + children.Single(), + childInfo: new[] + { + (51, "\"some value\"") // |> var other = new Other([|"some value"|]); [Code.cs:51] + }); + await ValidateChildrenEmptyAsync(workspace, children); + + // |> CallS(c, [|CalculateDefault(c)|] + _adornment) [Code.cs:28] + children = await ValidateChildrenAsync( + workspace, + items[3], + childInfo: new[] + { + (43, "\"\""), // |> return "" [Code.cs:37] + (40, "\"defaultstring\""), // |> return "defaultstring" [Code.cs:34] + (35, "\"null\""), // |> return "null" [Code.cs:29] + }); + + await ValidateChildrenEmptyAsync(workspace, children); + + // |> [|CallS(c, CalculateDefault(c) + _adornment)|] [Code.cs:28] + await ValidateChildrenEmptyAsync(workspace, items[4]); + } + + [Theory] + [CombinatorialData] + public async Task MethodTracking3(TestHost testHost) + { + var code = +@" +using System.Threading.Tasks; + +namespace N +{ + class C + { + public int Add(int x, int y) + { + x += y; + return x; + } + + public Task AddAsync(int x, int y) => Task.FromResult(Add(x,y)); + + public async Task Double(int x) + { + x = await AddAsync(x, x); + return $$x; + } + } +}"; + // + // |> return [|x|] [Code.cs:18] + // |> x = await AddAsync([|x|], x) [Code.cs:17] + // |> x = await AddAsync(x, [|x|]) [Code.cs:17] + // |> x = await [|AddAsync(x, x)|] [Code.cs:17] + // |> [|Task.FromResult|](Add(x, y)) [Code.cs:13] + // |> Task.FromResult([|Add(x, y)|]) [Code.cs:13] + // |> return x [Code.cs:11] + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + Assert.Equal(1, initialItems.Length); + ValidateItem(initialItems.Single(), 18, "x"); // |> return [|x|] [Code.cs:18] + + var children = await ValidateChildrenAsync( + workspace, + initialItems.Single(), + childInfo: new[] + { + (17, "x"), // |> x = await AddAsync([|x|], x) [Code.cs:17] + (17, "x"), // |> x = await AddAsync(x, [|x|]) [Code.cs:17] + (17, "AddAsync(x, x)") // |> x = await [|AddAsync(x, x)|] [Code.cs:17] + }); + + // |> x = await [|AddAsync(x, x)|] [Code.cs:17] + children = await ValidateChildrenAsync( + workspace, + children[2], + childInfo: new[] + { + (13, "x"), // |> Task.FromResult(Add([|x|], y)) [Code.cs:13] + (13, "y"), // |> Task.FromResult(Add(x, [|y|])) [Code.cs:13] + (13, "Add(x,y)"), // |> Task.FromResult([|Add(x, y)|]) [Code.cs:13] + (13, "Task.FromResult(Add(x,y))"), // |> [|Task.FromResult|](Add(x, y)) [Code.cs:13] + }); + + // |> [|Task.FromResult|](Add(x, y)) [Code.cs:13] + await ValidateChildrenEmptyAsync(workspace, children[3]); + + // |> Task.FromResult([|Add(x, y)|]) [Code.cs:13] + children = await ValidateChildrenAsync( + workspace, + children[2], + childInfo: new[] + { + (10, "x") // |> return x [Code.cs:10] + }); + } + + [Theory] + [CombinatorialData] + public async Task OutParam(TestHost testHost) + { + var code = @" +class C +{ + bool TryConvertInt(object o, out int i) + { + if (int.TryParse(o.ToString(), out i)) + { + return true; + } + + return false; + } + + void M() + { + int i = 0; + object o = ""5""; + + if (TryConvertInt(o, out i)) + { + Console.WriteLine($$i); + } + else + { + i = 2; + } + } +}"; + + // + // |> Console.WriteLine($$i); [Code.cs:20] + // |> i = [|2|] [Code.cs:24] + // |> if (TryConvertInt(o, out [|i|])) [Code.cs:18] + // |> if (int.TryParse(o.ToString(), out [|i|])) [Code.cs:5] + // |> int i = 0 [Code.cs:15] + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + Assert.Equal(1, initialItems.Length); + + // |> Console.WriteLine($$i);[Code.cs:20] + ValidateItem(initialItems.Single(), 20, "i"); + + var children = await ValidateChildrenAsync( + workspace, + initialItems.Single(), + childInfo: new[] + { + (24, "2"), // |> i = [|2|] [Code.cs:24] + (18, "i"), // |> if (TryConvertInt(o, out [|i|])) [Code.cs:18] + (15, "0"), // |> int i = 0 [Code.cs:15] + }); + + // |> i = [|2|] [Code.cs:24] + await ValidateChildrenEmptyAsync(workspace, children[0]); + + // |> if (TryConvertInt(o, out [|i|])) [Code.cs:18] + children = await ValidateChildrenAsync( + workspace, + children[1], + childInfo: new[] + { + (5, "i") // |> if (int.TryParse(o.ToString(), out [|i|])) [Code.cs:5] + }); + + await ValidateChildrenEmptyAsync(workspace, children.Single()); + } + + [Theory] + [CombinatorialData] + public async Task TestVariableReferenceStart(TestHost testHost) + { + var code = +@" +class Test +{ + public static void M() + { + int x = GetM(); + Console.Write(x); + var y = $$x + 1; + } + + public static int GetM() + { + var x = 0; + return x; + } +}"; + + // + // |> var y = x + 1; [Code.cs:7] + // |> int x = GetM() [Code.cs:5] + // |> return x; [Code.cs:13] + // |> var x = 0; [Code.cs:12] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (7, "x") // |> var y = [|x|] + 1; [Code.cs:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (5, "GetM()") // |> int x = [|GetM()|] [Code.cs:5] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (13, "x") // |> return [|x|]; [Code.cs:13] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (12, "0") // |> var x = [|0|]; [Code.cs:12] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + + [Theory] + [CombinatorialData] + public async Task TestVariableReferenceStart2(TestHost testHost) + { + var code = +@" +class Test +{ + public static void M() + { + int x = GetM(); + Console.Write($$x); + var y = x + 1; + } + + public static int GetM() + { + var x = 0; + return x; + } +}"; + + // + // |> Console.Write(x); [Code.cs:6] + // |> int x = GetM() [Code.cs:5] + // |> return x; [Code.cs:13] + // |> var x = 0; [Code.cs:12] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (6, "x") // |> Console.Write([|x|]); [Code.cs:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (5, "GetM()") // |> int x = [|GetM()|] [Code.cs:5] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (13, "x") // |> return [|x|]; [Code.cs:13] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (12, "0") // |> var x = [|0|]; [Code.cs:12] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + + [Theory] + [CombinatorialData] + public async Task TestVariableReferenceStart3(TestHost testHost) + { + var code = +@" +class Test +{ + public static void M() + { + int x = GetM(); + Console.Write($$x); + var y = x + 1; + x += 1; + Console.Write(x); + Console.Write(y); + } + + public static int GetM() + { + var x = 0; + return x; + } +}"; + + // + // |> Console.Write(x); [Code.cs:6] + // |> int x = GetM() [Code.cs:5] + // |> return x; [Code.cs:13] + // |> var x = 0; [Code.cs:12] + // |> x += 1 [Code.cs:8] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (6, "x") // |> Console.Write([|x|]); [Code.cs:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (8, "1"), // |> x += 1; [Codec.s:8] + (5, "GetM()"), // |> int x = [|GetM()|] [Code.cs:5] + }); + + await ValidateChildrenEmptyAsync(workspace, items[0]); + + items = await ValidateChildrenAsync( + workspace, + items[1], + childInfo: new[] + { + (16, "x") // |> return [|x|]; [Code.cs:13] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (15, "0") // |> var x = [|0|]; [Code.cs:12] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + + [Theory] + [CombinatorialData] + public async Task TestMultipleDeclarators(TestHost testHost) + { + var code = +@" +class Test +{ + public static void M() + { + int x = GetM(), z = 5; + Console.Write($$x); + var y = x + 1 + z; + x += 1; + Console.Write(x); + Console.Write(y); + } + + public static int GetM() + { + var x = 0; + return x; + } +}"; + + // + // |> Console.Write(x); [Code.cs:6] + // |> int x = GetM() [Code.cs:5] + // |> return x; [Code.cs:13] + // |> var x = 0; [Code.cs:12] + // |> x += 1 [Code.cs:8] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (6, "x") // |> Console.Write([|x|]); [Code.cs:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (8, "1"), // |> x += 1; [Codec.s:8] + (5, "GetM()"), // |> int x = [|GetM()|] [Code.cs:5] + }); + + await ValidateChildrenEmptyAsync(workspace, items[0]); + + items = await ValidateChildrenAsync( + workspace, + items[1], + childInfo: new[] + { + (16, "x") // |> return [|x|]; [Code.cs:13] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (15, "0") // |> var x = [|0|]; [Code.cs:12] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + } +} diff --git a/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs b/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs new file mode 100644 index 0000000000000..96cd454d6ffb0 --- /dev/null +++ b/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs @@ -0,0 +1,334 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.ValueTracking +{ + [UseExportProvider] + public class VisualBasicValueTrackingTests : AbstractBaseValueTrackingTests + { + protected override TestWorkspace CreateWorkspace(string code, TestComposition composition) + => TestWorkspace.CreateVisualBasic(code, composition: composition); + + [Theory] + [CombinatorialData] + public async Task TestProperty(TestHost testHost) + { + var code = +@" +Class C + Private _s As String + Public Property $$S() As String + Get + Return _s + End Get + Set(ByVal value As String) + _s = value + End Set + End Property + + + Public Sub SetS(s As String) + Me.S = s + End Sub + + Public Function GetS() As String + Return Me.S + End Function +End Class +"; + + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // property S + // |> Me.S = s [Code.vb:14] + // |> Public Property S() As String [Code.vb:3] + // + Assert.Equal(2, initialItems.Length); + ValidateItem(initialItems[0], 14); + ValidateItem(initialItems[1], 3); + } + + [Theory] + [CombinatorialData] + public async Task TestField(TestHost testHost) + { + var code = +@" +Class C + Private $$_s As String = """" + + Public Sub SetS(s As String) + Me._s = s + End Sub + + Public Function GetS() As String + Return Me._s + End Function +End Class +"; + + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // field _s + // |> Me._s = s [Code.vb:4] + // |> Private _s As String = "" [Code.vb:2] + // + await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (5, "s"), + (2, "_s") + }); + } + + [Theory] + [CombinatorialData] + public async Task TestLocal(TestHost testHost) + { + var code = +@" +Class C + Public Function Add(x As Integer, y As Integer) As Integer + Dim $$z = x + z += y + Return z + End Function +End Class +"; + + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // local variable z + // |> z += y [Code.vb:4] + // |> Dim z = x [Code.vb:3] + // + Assert.Equal(2, initialItems.Length); + ValidateItem(initialItems[0], 4); + ValidateItem(initialItems[1], 3); + } + + [Theory] + [CombinatorialData] + public async Task TestParameter(TestHost testHost) + { + var code = +@" +Class C + Public Function Add($$x As Integer, y As Integer) As Integer + x += y + Return x + End Function +End Class +"; + + using var workspace = CreateWorkspace(code, testHost); + var initialItems = await GetTrackedItemsAsync(workspace); + + // + // parameter x + // |> x += y [Code.vb:3] + // |> Public Function Add(x As integer, y As Integer) As Integer [Code.vb:2] + // + Assert.Equal(2, initialItems.Length); + ValidateItem(initialItems[0], 3); + ValidateItem(initialItems[1], 2); + } + + [Theory] + [CombinatorialData] + public async Task TestVariableReferenceStart(TestHost testHost) + { + var code = +@" +Class Test + Public Sub M() + Dim x = GetM() + Console.Write(x) + Dim y = $$x + 1 + End Sub + + Public Function GetM() As Integer + Dim x = 0 + Return x + End Function +End Class"; + + // + // |> Dim y = x + 1 [Code.vb:7] + // |> Dim x = GetM() [Code.vb:5] + // |> Return x; [Code.vb:13] + // |> Dim x = 0; [Code.vb:12] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (5, "x") // |> Dim y = [|x|] + 1; [Code.vb:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (3, "GetM()") // |> Dim x = [|GetM()|] [Code.vb:5] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (10, "x") // |> return [|x|]; [Code.vb:13] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (9, "0") // |> var x = [|0|]; [Code.vb:12] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + + [Theory] + [CombinatorialData] + public async Task TestVariableReferenceStart2(TestHost testHost) + { + var code = +@" +Class Test + Public Sub M() + Dim x = GetM() + Console.Write($$x) + Dim y = x + 1 + End Sub + + Public Function GetM() As Integer + Dim x = 0 + Return x + End Function +End Class"; + + // + // |> Dim y = x + 1 [Code.vb:7] + // |> Dim x = GetM() [Code.vb:5] + // |> Return x; [Code.vb:13] + // |> Dim x = 0; [Code.vb:12] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (4, "x") // |> Dim y = [|x|] + 1; [Code.vb:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (3, "GetM()") // |> Dim x = [|GetM()|] [Code.vb:5] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (10, "x") // |> return [|x|]; [Code.vb:13] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (9, "0") // |> var x = [|0|]; [Code.vb:12] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + + [Theory] + [CombinatorialData] + public async Task TestMultipleDeclarators(TestHost testHost) + { + var code = +@" +Imports System + +Class Test + Public Sub M() + Dim x = GetM(), z = 1, m As Boolean, n As Boolean, o As Boolean + Console.Write(x) + Dim y = $$x + 1 + End Sub + + Public Function GetM() As Integer + Dim x = 0 + Return x + End Function +End Class"; + + // + // |> Dim y = x + 1 [Code.vb:7] + // |> Dim x = GetM(), z = 1, m As Boolean, n As Boolean, o As Boolean [Code.vb:5] + // |> Return x; [Code.vb:12] + // |> Dim x = 0; [Code.vb:11] + using var workspace = CreateWorkspace(code, testHost); + + var items = await ValidateItemsAsync( + workspace, + itemInfo: new[] + { + (7, "x") // |> Dim y = [|x|] + 1; [Code.vb:7] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (5, "GetM()") // |> Dim x = [|GetM()|], z = 1, m As Boolean, n As Boolean, o As Boolean [Code.vb:5] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (12, "x") // |> return [|x|]; [Code.vb:12] + }); + + items = await ValidateChildrenAsync( + workspace, + items.Single(), + childInfo: new[] + { + (11, "0") // |> var x = [|0|]; [Code.vb:11] + }); + + await ValidateChildrenEmptyAsync(workspace, items.Single()); + } + } +} diff --git a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs index a01bff35edaa1..1f55e9d7d2332 100644 --- a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs @@ -30,7 +30,7 @@ private static void Test(Action new object()); @@ -113,7 +113,7 @@ public void TestCacheDoesNotKeepObjectsAliveAfterOwnerIsCollected2() public void TestImplicitCacheKeepsObjectAlive1() { var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cacheService = new ProjectCacheService(workspace, int.MaxValue); + var cacheService = new ProjectCacheService(workspace, TimeSpan.MaxValue); var reference = ObjectReference.CreateFromFactory(() => new object()); reference.UseReference(r => cacheService.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, r)); reference.AssertHeld(); @@ -125,7 +125,7 @@ public void TestImplicitCacheKeepsObjectAlive1() public void TestImplicitCacheMonitoring() { var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cacheService = new ProjectCacheService(workspace, 10); + var cacheService = new ProjectCacheService(workspace, TimeSpan.FromMilliseconds(10)); var weak = PutObjectInImplicitCache(cacheService); weak.AssertReleased(); @@ -156,7 +156,7 @@ public void TestP2PReference() var instanceTracker = ObjectReference.CreateFromFactory(() => new object()); - var cacheService = new ProjectCacheService(workspace, int.MaxValue); + var cacheService = new ProjectCacheService(workspace, TimeSpan.MaxValue); using (var cache = cacheService.EnableCaching(project2.Id)) { instanceTracker.UseReference(r => cacheService.CacheObjectIfCachingEnabledForKey(project1.Id, (object)null, r)); @@ -184,7 +184,7 @@ public void TestEjectFromImplicitCache() var weakLast = ObjectReference.Create(compilations[compilations.Count - 1]); var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cache = new ProjectCacheService(workspace, int.MaxValue); + var cache = new ProjectCacheService(workspace, TimeSpan.MaxValue); for (var i = 0; i < ProjectCacheService.ImplicitCacheSize + 1; i++) { cache.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, compilations[i]); @@ -212,7 +212,7 @@ public void TestCacheCompilationTwice() var weak1 = ObjectReference.Create(comp1); var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cache = new ProjectCacheService(workspace, int.MaxValue); + var cache = new ProjectCacheService(workspace, TimeSpan.MaxValue); var key = ProjectId.CreateNewId(); var owner = new object(); cache.CacheObjectIfCachingEnabledForKey(key, owner, comp1); diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index 6e1d67f374f09..34eae7c6195c6 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -357,6 +357,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests result.Add(SourceWarning(Id, message, documentId, documentId.ProjectId, mappedLine, originalLine, mappedColumn, originalColumn, mappedFile, originalFile)) End If Next + Return result End Function diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index a914934f45228..3bc784a48bacd 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -1648,6 +1648,7 @@ public class B If (symbols.Contains(sc.Symbol)) Then Throw New Exception("Duplicate symbol callback") End If + sc.ReportDiagnostic(Diagnostic.Create(DiagDescriptor, sc.Symbol.Locations.First())) End Sub, SymbolKind.NamedType) End Sub) diff --git a/src/EditorFeatures/Test2/Expansion/AbstractExpansionTest.vb b/src/EditorFeatures/Test2/Expansion/AbstractExpansionTest.vb index b9b2ff8c90a3b..f2ddb6aadf2ff 100644 --- a/src/EditorFeatures/Test2/Expansion/AbstractExpansionTest.vb +++ b/src/EditorFeatures/Test2/Expansion/AbstractExpansionTest.vb @@ -57,6 +57,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Expansion Exit While End If End While + Return node End Function diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.EventSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.EventSymbols.vb index 00372e514edbd..520dbba6235ca 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.EventSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.EventSymbols.vb @@ -316,5 +316,150 @@ End Interface Await TestAPIAndFeature(input, kind, host) End Function + + + Public Async Function TestStaticAbstractEventInInterface(kind As TestKind, host As TestHost) As Task + Dim input = + + + +interface I3 +{ + abstract static event System.Action {|Definition:E$$3|}; +} + +class C3_1 : I3 +{ + public static event System.Action {|Definition:E3|}; +} + +class C3_2 : I3 +{ + static event System.Action I3.{|Definition:E3|} + { + add { } + remove { } + } +} + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestStaticAbstractEventViaFeature1(host As TestHost) As Task + Dim input = + + + +interface I3 +{ + abstract static event System.Action {|Definition:E3|}; +} + +class C3_1 : I3 +{ + public static event System.Action {|Definition:E$$3|}; +} + +class C3_2 : I3 +{ + static event System.Action I3.E3 + { + add { } + remove { } + } +} + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestStaticAbstractEventViaFeature2(host As TestHost) As Task + Dim input = + + + +interface I3 +{ + abstract static event System.Action {|Definition:E3|}; +} + +class C3_1 : I3 +{ + public static event System.Action E3; +} + +class C3_2 : I3 +{ + static event System.Action I3.{|Definition:E$$3|} + { + add { } + remove { } + } +} + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestStaticAbstractEventViaAPI1(host As TestHost) As Task + Dim input = + + + +interface I3 +{ + abstract static event System.Action {|Definition:E3|}; +} + +class C3_1 : I3 +{ + public static event System.Action {|Definition:E3|}; +} + +class C3_2 : I3 +{ + static event System.Action I3.{|Definition:E$$3|} + { + add { } + remove { } + } +} + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestStaticAbstractEventViaAPI2(host As TestHost) As Task + Dim input = + + + +interface I3 +{ + abstract static event System.Action {|Definition:E3|}; +} + +class C3_1 : I3 +{ + public static event System.Action {|Definition:E$$3|}; +} + +class C3_2 : I3 +{ + static event System.Action I3.{|Definition:E3|} + { + add { } + remove { } + } +} + + + Await TestAPI(input, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb index dabf62fca6418..847bd34707da8 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb @@ -483,5 +483,274 @@ class B Await TestAPIAndFeature(input, kind, host) End Function + + Public Async Function TestCSharpStaticAbstractConversionOperatorInInterface(kind As TestKind, host As TestHost) As Task + Dim input = + + + + where T : I5 +{ + abstract static implicit operator {|Definition:i$$nt|}(T x); +} + +class C5_1 : I5 +{ + public static implicit operator {|Definition:int|}(C5_1 x) => default; +} + +class C5_2 : I5 +{ + static implicit I5.operator {|Definition:int|}(C5_2 x) => default; +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestCSharpStaticAbstractConversionOperatorViaFeature1(host As TestHost) As Task + Dim input = + + + + where T : I5 +{ + abstract static implicit operator {|Definition:int|}(T x); +} + +class C5_1 : I5 +{ + public static implicit operator {|Definition:i$$nt|}(C5_1 x) => default; +} + +class C5_2 : I5 +{ + static implicit I5.operator int(C5_2 x) => default; +}]]> + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractConversionOperatorViaFeature2(host As TestHost) As Task + Dim input = + + + + where T : I5 +{ + abstract static implicit operator {|Definition:int|}(T x); +} + +class C5_1 : I5 +{ + public static implicit operator int(C5_1 x) => default; +} + +class C5_2 : I5 +{ + static implicit I5.operator {|Definition:i$$nt|}(C5_2 x) => default; +}]]> + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractConversionOperatorViaApi1(host As TestHost) As Task + Dim input = + + + + where T : I5 +{ + abstract static implicit operator {|Definition:int|}(T x); +} + +class C5_1 : I5 +{ + public static implicit operator {|Definition:int|}(C5_1 x) => default; +} + +class C5_2 : I5 +{ + static implicit I5.operator {|Definition:i$$nt|}(C5_2 x) => default; +}]]> + + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractConversionOperatorViaApi2(host As TestHost) As Task + Dim input = + + + + where T : I5 +{ + abstract static implicit operator {|Definition:int|}(T x); +} + +class C5_1 : I5 +{ + public static implicit operator {|Definition:in$$t|}(C5_1 x) => default; +} + +class C5_2 : I5 +{ + static implicit I5.operator {|Definition:int|}(C5_2 x) => default; +}]]> + + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractOperatorInInterface(kind As TestKind, host As TestHost) As Task + Dim input = + + + + where T : I4 +{ + abstract static int operator {|Definition:+$$|}(T x); +} + +class C4_1 : I4 +{ + public static int operator {|Definition:+|}(C4_1 x) => default; +} + +class C4_2 : I4 +{ + static int I4.operator {|Definition:+|}(C4_2 x) => default; +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestCSharpStaticAbstractOperatorViaApi1(host As TestHost) As Task + Dim input = + + + + where T : I4 +{ + abstract static int operator {|Definition:+|}(T x); +} + +class C4_1 : I4 +{ + public static int operator {|Definition:$$+|}(C4_1 x) => default; +} + +class C4_2 : I4 +{ + static int I4.operator {|Definition:+|}(C4_2 x) => default; +}]]> + + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractOperatorViaApi2(host As TestHost) As Task + Dim input = + + + + where T : I4 +{ + abstract static int operator {|Definition:+|}(T x); +} + +class C4_1 : I4 +{ + public static int operator {|Definition:+|}(C4_1 x) => default; +} + +class C4_2 : I4 +{ + static int I4.operator {|Definition:$$+|}(C4_2 x) => default; +}]]> + + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractOperatorViaFeature1(host As TestHost) As Task + Dim input = + + + + where T : I4 +{ + abstract static int operator {|Definition:+|}(T x); +} + +class C4_1 : I4 +{ + public static int operator {|Definition:$$+|}(C4_1 x) => default; +} + +class C4_2 : I4 +{ + static int I4.operator +(C4_2 x) => default; +}]]> + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestCSharpStaticAbstractOperatorViaFeature2(host As TestHost) As Task + Dim input = + + + + where T : I4 +{ + abstract static int operator {|Definition:+|}(T x); +} + +class C4_1 : I4 +{ + public static int operator +(C4_1 x) => default; +} + +class C4_2 : I4 +{ + static int I4.operator {|Definition:$$+|}(C4_2 x) => default; +}]]> + + + + Await TestStreamingFeature(input, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb index 159df9d86c166..b56ee8a7847a3 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb @@ -3786,5 +3786,125 @@ End Class Await TestStreamingFeature(input, host) End Function + + + Public Async Function TestMemberStaticAbstractMethodFromInterface(kind As TestKind, host As TestHost) As Task + Dim input = + + + + interface I1 + { + static abstract void {|Definition:M$$1|}(); + } + class C1_1 : I1 + { + public static void {|Definition:M1|}() { } + } + class C1_2 : I1 + { + static void I1.{|Definition:M1|}() { } + } + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestDerivedMemberStaticAbstractMethodViaFeature1(host As TestHost) As Task + Dim input = + + + + interface I1 + { + static abstract void {|Definition:M1|}(); + } + class C1_1 : I1 + { + public static void {|Definition:M$$1|}() { } + } + class C1_2 : I1 + { + static void I1.M1() { } + } + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestDerivedMemberStaticAbstractMethodViaFeature2(host As TestHost) As Task + Dim input = + + + + interface I1 + { + static abstract void {|Definition:M1|}(); + } + class C1_1 : I1 + { + public static void M1() { } + } + class C1_2 : I1 + { + static void I1.{|Definition:M$$1|}() { } + } + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestDerivedMemberStaticAbstractMethodViaAPI1(host As TestHost) As Task + Dim input = + + + + interface I1 + { + static abstract void {|Definition:M1|}(); + } + class C1_1 : I1 + { + public static void {|Definition:M$$1|}() { } + } + class C1_2 : I1 + { + static void I1.{|Definition:M1|}() { } + } + + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestDerivedMemberStaticAbstractMethodViaAPI2(host As TestHost) As Task + Dim input = + + + + interface I1 + { + static abstract void {|Definition:M1|}(); + } + class C1_1 : I1 + { + public static void {|Definition:M1|}() { } + } + class C1_2 : I1 + { + static void I1.{|Definition:M$$1|}() { } + } + + + + Await TestAPI(input, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb index 68ce3668624f5..9faa9028e087d 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb @@ -1084,5 +1084,135 @@ namespace ConsoleApplication22 Await TestAPIAndFeature(input, kind, host) End Function + + + Public Async Function TestCSharp_AbstractStaticPropertyInInterface(kind As TestKind, host As TestHost) As Task + Dim input = + + + +interface I2 +{ + abstract static int {|Definition:P$$2|} { get; set; } +} + +class C2_1 : I2 +{ + public static int {|Definition:P2|} { get; set; } +} + +class C2_2 : I2 +{ + static int I2.{|Definition:P2|} { get; set; } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + Public Async Function TestCSharp_AbstractStaticPropertyViaFeature1(host As TestHost) As Task + Dim input = + + + +interface I2 +{ + abstract static int {|Definition:P2|} { get; set; } +} + +class C2_1 : I2 +{ + public static int {|Definition:P$$2|} { get; set; } +} + +class C2_2 : I2 +{ + static int I2.P2 { get; set; } +} + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestCSharp_AbstractStaticPropertyViaFeature2(host As TestHost) As Task + Dim input = + + + +interface I2 +{ + abstract static int {|Definition:P2|} { get; set; } +} + +class C2_1 : I2 +{ + public static int P2 { get; set; } +} + +class C2_2 : I2 +{ + static int I2.{|Definition:P$$2|} { get; set; } +} + + + + Await TestStreamingFeature(input, host) + End Function + + + Public Async Function TestCSharp_AbstractStaticPropertyViaAPI1(host As TestHost) As Task + Dim input = + + + +interface I2 +{ + abstract static int {|Definition:P2|} { get; set; } +} + +class C2_1 : I2 +{ + public static int {|Definition:P2|} { get; set; } +} + +class C2_2 : I2 +{ + static int I2.{|Definition:P$$2|} { get; set; } +} + + + + Await TestAPI(input, host) + End Function + + + Public Async Function TestCSharp_AbstractStaticPropertyViaAPI2(host As TestHost) As Task + Dim input = + + + +interface I2 +{ + abstract static int {|Definition:P2|} { get; set; } +} + +class C2_1 : I2 +{ + public static int {|Definition:P$$2|} { get; set; } +} + +class C2_2 : I2 +{ + static int I2.{|Definition:P2|} { get; set; } +} + + + + Await TestAPI(input, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb index a8cc26e9efcf4..e88e0ee842ffd 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb @@ -169,6 +169,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences propertyValues = New HashSet(Of String)() additionalPropertiesMap.Add(propertyName, propertyValues) End If + propertyValues.Add(propertyValue) Next @@ -438,6 +439,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences builder.Append(suffix) position = span.End Next + builder.Append(text.GetSubText(New TextSpan(position, text.Length - position))) Return instance.ToStringAndFree() diff --git a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb index 66602cf6999fb..f5e8c6909494c 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb @@ -1777,6 +1777,7 @@ namespace QueryPattern If highlight = "" Then Return definition End If + Dim searchStartPosition As Integer = 0 Dim searchFound As Integer For i As Integer = 0 To index @@ -1785,11 +1786,13 @@ namespace QueryPattern Exit For End If Next + If searchFound >= 0 Then definition = definition.Insert(searchFound + highlight.Length, "|]") definition = definition.Insert(searchFound, "[|") Return definition End If + Throw New InvalidOperationException("Highlight not found") End Function diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index a75a77c6f2ee1..b9fe83b42bd23 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -482,6 +482,7 @@ record Derived(int Other) : [|Base$$|] If showCompletionInArgumentLists Then Await state.AssertSelectedCompletionItem(displayText:="Alice:", isHardSelected:=True) End If + state.SendTypeChars(": 1, B") If showCompletionInArgumentLists Then @@ -800,6 +801,7 @@ class C state.SendBackspace() Await state.WaitForAsynchronousOperationsAsync() Next + Await state.AssertCompletionSession() state.SendBackspace() @@ -2335,6 +2337,7 @@ public class C Else Await state.AssertNoCompletionSession() End If + state.SendTypeChars("A") Await state.AssertSelectedCompletionItem(displayText:="Alice:", isHardSelected:=True) state.SendTypeChars(":") @@ -4887,6 +4890,7 @@ class C For i = 1 To "ConfigureAwait".Length state.SendBackspace() Next + state.SendInvokeCompletionList() Await state.AssertCompletionSession() Await state.AssertSelectedCompletionItem("ConfigureAwait") @@ -5156,6 +5160,7 @@ class C For i As Integer = 1 To "System.".Length state.SendBackspace() Next + state.SendEscape() Await state.WaitForAsynchronousOperationsAsync() @@ -5177,6 +5182,7 @@ class C For i As Integer = 1 To "Sys.".Length state.SendBackspace() Next + state.SendEscape() Await state.WaitForAsynchronousOperationsAsync() @@ -6565,12 +6571,14 @@ class C For i = 1 To "ze".Length state.SendBackspace() Next + Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()") state.SendEscape() For i = 1 To "Normali".Length state.SendBackspace() Next + state.SendEscape() Assert.Contains("s.", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) @@ -6619,12 +6627,14 @@ class C For i = 1 To "ze".Length state.SendBackspace() Next + Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()") state.SendEscape() For i = 1 To "Normali".Length state.SendBackspace() Next + state.SendEscape() Assert.Contains("s.", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_InternalsVisibleTo.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_InternalsVisibleTo.vb index 125627f1a80fe..345363dd95215 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_InternalsVisibleTo.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_InternalsVisibleTo.vb @@ -165,6 +165,7 @@ namespace A state.SendInvokeCompletionList() Await state.AssertSessionIsNothingOrNoCompletionItemLike("ClassLibrary1") End Function + Await AssertNoCompletionAndCompletionDoesNotContainClassLibrary1() state.SendTypeChars("["c) diff --git a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests_Code.vb b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests_Code.vb new file mode 100644 index 0000000000000..25d531e754130 --- /dev/null +++ b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests_Code.vb @@ -0,0 +1,113 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Classification +Imports Microsoft.CodeAnalysis.Test.Utilities.QuickInfo +Imports Microsoft.VisualStudio.Core.Imaging +Imports Microsoft.VisualStudio.Imaging +Imports Microsoft.VisualStudio.Text.Adornments + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense + Public Class IntellisenseQuickInfoBuilderTests_Code + Inherits AbstractIntellisenseQuickInfoBuilderTests + + Public Async Function QuickInfoForXmlCodeElementWithCDATA() As Task + Dim workspace = + + + + class MyClass { + /// <summary> + /// summary for MyClass + /// <code><![CDATA[ + /// List<string> y = null; + /// ]]></code> + /// </summary> + void MyMethod() { + MyM$$ethod(); + } + } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Stacked, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPrivate)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "void"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ClassName, "MyClass", navigationAction:=Sub() Return, "MyClass"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."), + New ClassifiedTextRun(ClassificationTypeNames.MethodName, "MyMethod", navigationAction:=Sub() Return, "void MyClass.MyMethod()"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"))), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Text, "summary for MyClass"))), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Text, "List y = null;", ClassifiedTextRunStyle.UseClassificationFont))) + + ToolTipAssert.EqualContent(expected, intellisenseQuickInfo.Item) + End Function + + + Public Async Function QuickInfoForXmlCodeElement() As Task + Dim workspace = + + + + class MyClass { + /// <summary> + /// Normalize this, <c>and also + /// this</c> + /// <code> + /// line 1 + /// line 2 + /// </code> + /// Extra text after code. + /// </summary> + void MyMethod() { + MyM$$ethod(); + } + } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Stacked, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPrivate)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "void"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ClassName, "MyClass", navigationAction:=Sub() Return, "MyClass"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."), + New ClassifiedTextRun(ClassificationTypeNames.MethodName, "MyMethod", navigationAction:=Sub() Return, "void MyClass.MyMethod()"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"))), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Text, "Normalize this,"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Text, "and also this", ClassifiedTextRunStyle.UseClassificationFont))), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Text, $"line 1{vbCrLf}line 2", ClassifiedTextRunStyle.UseClassificationFont)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Text, "Extra text after code."))) + + ToolTipAssert.EqualContent(expected, intellisenseQuickInfo.Item) + End Function + End Class +End Namespace diff --git a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb index d86ffcc83399b..3417e203d97e8 100644 --- a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb @@ -191,6 +191,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense GetMocks(controller).PresenterSession.Verify(Sub(p) p.PresentItems(It.IsAny(Of ITrackingSpan), It.IsAny(Of IList(Of SignatureHelpItem)), It.IsAny(Of SignatureHelpItem), It.IsAny(Of Integer?)), Times.Exactly(2)) End Function + Await worker().ConfigureAwait(False) End Function diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_InternalsVisibleTo.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_InternalsVisibleTo.vb index b242ce8cddddb..fd9dd6ecc968b 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_InternalsVisibleTo.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_InternalsVisibleTo.vb @@ -161,6 +161,7 @@ End Namespace state.SendInvokeCompletionList() Await state.AssertSessionIsNothingOrNoCompletionItemLike("ClassLibrary1") End Function + Await AssertNoCompletionAndCompletionDoesNotContainClassLibrary1() state.SendTypeChars("("c) Await state.AssertCompletionItemsDoNotContainAny("ClassLibrary1") diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb index d1a0c98dfb719..d4a6efe26e574 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb @@ -663,6 +663,7 @@ End Class Await state.AssertSelectedCompletionItem(displayText:=keyword) state.SendTab() End If + Await state.AssertNoCompletionSession() ' ''' $$ diff --git a/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb b/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb index 41de7ce21209e..4cb6c57e81e07 100644 --- a/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb @@ -236,12 +236,15 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste If includeRepl Then data.SetData(InteractiveClipboardFormat.Tag, json) End If + If isLineCopy Then data.SetData(ClipboardLineBasedCutCopyTag, True) End If + If isBoxCopy Then data.SetData(BoxSelectionCutCopyTag, True) End If + clipboard.SetDataObject(data) End Sub diff --git a/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb b/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb index 160317af06e3a..658b8e38c47a3 100644 --- a/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb +++ b/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb @@ -45,6 +45,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar If _presentItemsCallback IsNot Nothing Then _presentItemsCallback() End If + If _presentItemsWithValuesCallback IsNot Nothing Then _presentItemsWithValuesCallback(projects, selectedProject, typesWithMembers, selectedType, selectedMember) End If diff --git a/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb b/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb index cfb2f43dd403a..d11df11f74712 100644 --- a/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb +++ b/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb @@ -117,8 +117,6 @@ class C Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, baseDocument.GetTextBuffer()) - controller.SetWorkspace(workspace) - Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider).Value Dim workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) Dim navbarWaiter = listenerProvider.GetWaiter(FeatureAttribute.NavigationBar) @@ -186,8 +184,6 @@ End Class Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, baseDocument.GetTextBuffer()) - controller.SetWorkspace(workspace) - Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider).Value Dim workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) Dim navbarWaiter = listenerProvider.GetWaiter(FeatureAttribute.NavigationBar) @@ -324,7 +320,6 @@ End Class Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, document.GetTextBuffer()) - controller.SetWorkspace(workspace) mockPresenter.RaiseDropDownFocused() Assert.Equal("VBProj", projectName) diff --git a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb index c408f4ca61337..0e2fbcb18e492 100644 --- a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb +++ b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb @@ -59,7 +59,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar Dim items = Await service.GetItemsAsync(document, snapshot, Nothing) Dim hostDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim model As New NavigationBarModel(items.ToImmutableArray(), VersionStamp.Create(), service) + Dim model As New NavigationBarModel(items.ToImmutableArray(), service) Dim selectedItems = NavigationBarController.ComputeSelectedTypeAndMember(model, New SnapshotPoint(hostDocument.GetTextBuffer().CurrentSnapshot, hostDocument.CursorPosition.Value), Nothing) Dim isCaseSensitive = document.GetLanguageService(Of ISyntaxFactsService)().IsCaseSensitive diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 260a14bb4a1bf..22c182bb65c00 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -109,6 +109,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Else workspace.Dispose() End If + Throw End Try diff --git a/src/EditorFeatures/Test2/Rename/RenameTagProducerTests.vb b/src/EditorFeatures/Test2/Rename/RenameTagProducerTests.vb index cd00dad929025..5685ad4f6db0d 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTagProducerTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTagProducerTests.vb @@ -54,6 +54,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename If kvp.Key = annotationString Then Return kvp.Value.Select(Function(ts) ts.ToSpan()) End If + Return SpecializedCollections.EmptyEnumerable(Of Span) End Function) End Function diff --git a/src/EditorFeatures/Test2/Simplification/AbstractSimplificationTests.vb b/src/EditorFeatures/Test2/Simplification/AbstractSimplificationTests.vb index da51334eba84b..4a23a6a4834a2 100644 --- a/src/EditorFeatures/Test2/Simplification/AbstractSimplificationTests.vb +++ b/src/EditorFeatures/Test2/Simplification/AbstractSimplificationTests.vb @@ -31,6 +31,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Simplification workspace.ChangeSolution(workspace.CurrentSolution.WithProjectParseOptions(project.Id, csharpParseOptions)) Next End If + Return workspace End Function @@ -135,6 +136,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Simplification Exit While End If End While + Return node End Function diff --git a/src/EditorFeatures/TestUtilities/Classification/FormattedClassification.cs b/src/EditorFeatures/TestUtilities/Classification/FormattedClassification.cs index 02c112ab14f16..8a023df776fb1 100644 --- a/src/EditorFeatures/TestUtilities/Classification/FormattedClassification.cs +++ b/src/EditorFeatures/TestUtilities/Classification/FormattedClassification.cs @@ -66,6 +66,7 @@ public override string ToString() case ",": return "Punctuation.Comma"; } + goto default; case "operator": @@ -76,6 +77,7 @@ public override string ToString() case "++": return "Operators.PlusPlus"; } + goto default; default: diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs index 58da93e20c6d4..6d71e27b24d19 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs @@ -56,6 +56,7 @@ public GenerateTypeOptionsResult GetGenerateTypeOptions( { DefaultNamespace = projectManagementService.GetDefaultNamespace(Project, Project?.Solution.Workspace); } + return new GenerateTypeOptionsResult( accessibility: Accessibility, typeKind: TypeKind, diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index fe39597f7c5a2..88b41f12b85b4 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -24,11 +24,17 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests internal abstract class EditAndContinueTestHelpers { public static readonly EditAndContinueCapabilities BaselineCapabilities = EditAndContinueCapabilities.Baseline; - public static readonly EditAndContinueCapabilities Net5RuntimeCapabilities = EditAndContinueCapabilities.Baseline | - EditAndContinueCapabilities.AddInstanceFieldToExistingType | - EditAndContinueCapabilities.AddStaticFieldToExistingType | - EditAndContinueCapabilities.AddMethodToExistingType | - EditAndContinueCapabilities.NewTypeDefinition; + + public static readonly EditAndContinueCapabilities Net5RuntimeCapabilities = + EditAndContinueCapabilities.Baseline | + EditAndContinueCapabilities.AddInstanceFieldToExistingType | + EditAndContinueCapabilities.AddStaticFieldToExistingType | + EditAndContinueCapabilities.AddMethodToExistingType | + EditAndContinueCapabilities.NewTypeDefinition; + + public static readonly EditAndContinueCapabilities Net6RuntimeCapabilities = + Net5RuntimeCapabilities | + EditAndContinueCapabilities.ChangeCustomAttributes; public abstract AbstractEditAndContinueAnalyzer Analyzer { get; } public abstract SyntaxNode FindNode(SyntaxNode root, TextSpan span); diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 347ddf2d2d659..988a4ea86afb6 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -40,6 +40,7 @@ public abstract class AbstractLanguageServerProtocolTests private static readonly TestComposition s_composition = EditorTestCompositions.LanguageServerProtocolWpf .AddParts(typeof(TestLspWorkspaceRegistrationService)) .AddParts(typeof(TestDocumentTrackingService)) + .AddParts(typeof(TestExperimentationService)) .RemoveParts(typeof(MockWorkspaceEventListenerProvider)); [Export(typeof(ILspWorkspaceRegistrationService)), PartNotDiscoverable] @@ -411,6 +412,49 @@ private static RequestExecutionQueue CreateRequestQueue(TestWorkspace workspace) private static string GetDocumentFilePathFromName(string documentName) => "C:\\" + documentName; + private static LSP.DidChangeTextDocumentParams CreateDidChangeTextDocumentParams( + Uri documentUri, + ImmutableArray<(int startLine, int startColumn, int endLine, int endColumn, string text)> changes) + { + var changeEvents = changes.Select(change => new LSP.TextDocumentContentChangeEvent + { + Text = change.text, + Range = new LSP.Range + { + Start = new LSP.Position(change.startLine, change.startColumn), + End = new LSP.Position(change.endLine, change.endColumn) + } + }).ToArray(); + + return new LSP.DidChangeTextDocumentParams() + { + TextDocument = new LSP.VersionedTextDocumentIdentifier + { + Uri = documentUri + }, + ContentChanges = changeEvents + }; + } + + private static LSP.DidOpenTextDocumentParams CreateDidOpenTextDocumentParams(Uri uri, string source) + => new LSP.DidOpenTextDocumentParams + { + TextDocument = new LSP.TextDocumentItem + { + Text = source, + Uri = uri + } + }; + + private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(Uri uri) + => new LSP.DidCloseTextDocumentParams() + { + TextDocument = new LSP.TextDocumentIdentifier + { + Uri = uri + } + }; + public sealed class TestLspServer : IDisposable { public readonly TestWorkspace TestWorkspace; @@ -431,6 +475,41 @@ public Task ExecuteRequestAsync(string _executionQueue, methodName, request, clientCapabilities, clientName, cancellationToken); } + public async Task OpenDocumentAsync(Uri documentUri) + { + // LSP open files don't care about the project context, just the file contents with the URI. + // So pick any of the linked documents to get the text from. + var text = await TestWorkspace.CurrentSolution.GetDocuments(documentUri).First().GetTextAsync(CancellationToken.None).ConfigureAwait(false); + var didOpenParams = CreateDidOpenTextDocumentParams(documentUri, text.ToString()); + await ExecuteRequestAsync(LSP.Methods.TextDocumentDidOpenName, + didOpenParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + } + + public Task InsertTextAsync(Uri documentUri, params (int line, int column, string text)[] changes) + { + var didChangeParams = CreateDidChangeTextDocumentParams( + documentUri, + changes.Select(change => (startLine: change.line, startColumn: change.column, endLine: change.line, endColumn: change.column, change.text)).ToImmutableArray()); + return ExecuteRequestAsync(LSP.Methods.TextDocumentDidChangeName, + didChangeParams, new LSP.ClientCapabilities(), clientName: null, CancellationToken.None); + } + + public Task DeleteTextAsync(Uri documentUri, params (int startLine, int startColumn, int endLine, int endColumn)[] changes) + { + var didChangeParams = CreateDidChangeTextDocumentParams( + documentUri, + changes.Select(change => (change.startLine, change.startColumn, change.endLine, change.endColumn, text: string.Empty)).ToImmutableArray()); + return ExecuteRequestAsync(LSP.Methods.TextDocumentDidChangeName, + didChangeParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + } + + public Task CloseDocumentAsync(Uri documentUri) + { + var didCloseParams = CreateDidCloseTextDocumentParams(documentUri); + return ExecuteRequestAsync(LSP.Methods.TextDocumentDidCloseName, + didCloseParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + } + public Solution GetCurrentSolution() => TestWorkspace.CurrentSolution; internal RequestExecutionQueue.TestAccessor GetQueueAccessor() => _executionQueue.GetTestAccessor(); diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/MockCompletionPresenterProvider.vb b/src/EditorFeatures/TestUtilities2/Intellisense/MockCompletionPresenterProvider.vb index 17d6077d70538..cc52445075dbe 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/MockCompletionPresenterProvider.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/MockCompletionPresenterProvider.vb @@ -37,6 +37,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense If Not _presenters.ContainsKey(textView) Then _presenters(textView) = New MockCompletionPresenter(textView) End If + Return _presenters(textView) End Function End Class diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb index 42721cc4584c3..851f6c629ba33 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb @@ -249,6 +249,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense End Sub) End If End Sub + Dim sessionDismissedHandler = Sub(sender As Object, e As EventArgs) sessionComplete.TrySetResult(Nothing) Dim session As IAsyncCompletionSession diff --git a/src/EditorFeatures/Text/Microsoft.CodeAnalysis.EditorFeatures.Text.csproj b/src/EditorFeatures/Text/Microsoft.CodeAnalysis.EditorFeatures.Text.csproj index 98ac2d73b3bf1..e5cccc88f95fc 100644 --- a/src/EditorFeatures/Text/Microsoft.CodeAnalysis.EditorFeatures.Text.csproj +++ b/src/EditorFeatures/Text/Microsoft.CodeAnalysis.EditorFeatures.Text.csproj @@ -5,7 +5,7 @@ Library Microsoft.CodeAnalysis.Text netcoreapp3.1;netstandard2.0 - partial + partial true diff --git a/src/EditorFeatures/VisualBasic/EndConstructGeneration/EndConstructExtensions.vb b/src/EditorFeatures/VisualBasic/EndConstructGeneration/EndConstructExtensions.vb index 2836e02d7c4a4..034f0679331a8 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/EndConstructExtensions.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/EndConstructExtensions.vb @@ -13,6 +13,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration If snapshot Is Nothing Then Throw New ArgumentNullException(NameOf(snapshot)) End If + Dim line = snapshot.GetLineFromPosition(position) Dim precedingText = snapshot.GetText(Span.FromBounds(line.Start, position)) diff --git a/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb b/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb index b8ab1ae14e182..c4ef6e59106d3 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/VisualBasicEndConstructGenerationService.vb @@ -363,6 +363,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration If errors.Count <> 1 Then Return False End If + If Not IsExpectedXmlEndCDataError(errors(0).Id) Then Return False End If @@ -390,6 +391,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration If errors.Count <> 1 Then Return False End If + If Not IsExpectedXmlEndCommentError(errors(0).Id) Then Return False End If @@ -417,6 +419,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration If errors.Count <> 1 Then Return False End If + If Not IsMissingXmlEndTagError(errors(0).Id) Then Return False End If @@ -468,6 +471,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration If errors.Count <> 2 Then Return False End If + If Not (errors.Any(Function(e) IsExpectedXmlNameError(e.Id)) AndAlso errors.Any(Function(e) IsExpectedXmlEndPIError(e.Id))) Then Return False diff --git a/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/TryBlockHighlighter.vb b/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/TryBlockHighlighter.vb index dcff6f2786ca5..05d606df0f3db 100644 --- a/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/TryBlockHighlighter.vb +++ b/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/TryBlockHighlighter.vb @@ -42,6 +42,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.KeywordHighlighting highlights.Add(.WhenClause.WhenKeyword.Span) End If End With + HighlightRelatedStatements(catchBlock, highlights) Next diff --git a/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/XmlElementHighlighter.vb b/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/XmlElementHighlighter.vb index 172c51b171684..c09dfc1d81a1b 100644 --- a/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/XmlElementHighlighter.vb +++ b/src/EditorFeatures/VisualBasic/Highlighting/KeywordHighlighters/XmlElementHighlighter.vb @@ -34,6 +34,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.KeywordHighlighting highlights.Add(.GreaterThanToken.Span) End If End With + highlights.Add(.EndTag.Span) End If diff --git a/src/EditorFeatures/VisualBasic/LineSeparators/VisualBasicLineSeparatorService.vb b/src/EditorFeatures/VisualBasic/LineSeparators/VisualBasicLineSeparatorService.vb index 04ab654c141d7..2d7233579bbc9 100644 --- a/src/EditorFeatures/VisualBasic/LineSeparators/VisualBasicLineSeparatorService.vb +++ b/src/EditorFeatures/VisualBasic/LineSeparators/VisualBasicLineSeparatorService.vb @@ -114,6 +114,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineSeparators Dim prev = children(i - 1) spans.Add(GetLineSeparatorSpanForNode(prev)) End If + spans.Add(GetLineSeparatorSpanForNode(cur)) seenSeparator = True @@ -128,6 +129,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineSeparators Dim nextToLast = children(children.Count - 2) spans.Add(GetLineSeparatorSpanForNode(nextToLast)) End If + If lastChild.Parent.Kind = SyntaxKind.CompilationUnit Then spans.Add(GetLineSeparatorSpanForNode(lastChild)) End If diff --git a/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj index 9a3cb1ca4f787..528fe72cace76 100644 --- a/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj @@ -5,7 +5,7 @@ Library netcoreapp3.1;netstandard2.0 - partial + partial true diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb index c55b3c42b7b87..b69c77b032198 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb @@ -931,6 +931,18 @@ End Class" Await TestInRegularAndScriptAsync(source, expected, index:=1) End Function + + + Public Async Function TestHighlightReturnType() As Task + Dim source = +"Class Program + Public Function M(x As Integer) As [|Integer|] + Return x + End Function +End Class" + + Await TestMissingInRegularAndScriptAsync(source) + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb index 276120645efd8..1f0dfb55aaa06 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb @@ -89,6 +89,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet If expectedItemOrNull(0) = "[" Then Return expectedItemOrNull.Substring(1, 1) End If + Return expectedItemOrNull.Substring(0, 1) End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb index d9003472ed7e5..348edfce5540e 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb @@ -6340,6 +6340,34 @@ End Class]]>.Value Await VerifyItemExistsAsync(text, "ToString") End Function + + + Public Async Function TestConditionalOperatorCompletionForNullableParameterSymbols_1() As Task + Dim text = +.Value + Await VerifyItemExistsAsync(text, "Day") + Await VerifyItemIsAbsentAsync(text, "Value") + End Function + + + + Public Async Function TestConditionalOperatorCompletionForNullableParameterSymbols_2() As Task + Dim text = +.Value + Await VerifyItemExistsAsync(text, "Value") + Await VerifyItemIsAbsentAsync(text, "Day") + End Function + Public Async Function TestHidePropertyBackingFieldAndEventsAtExpressionLevel() As Task @@ -7750,6 +7778,7 @@ End Namespace For index = 2 To 8 Await VerifyItemExistsAsync(text, "Item" + index.ToString()) Next + Await VerifyItemExistsAsync(text, "ToString") Await VerifyItemIsAbsentAsync(text, "Item1") diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index aecb17e8655b2..1a465c7cc1b3a 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -1945,7 +1945,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Const a As Integer = 1", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As Integer = 1", FeaturesResources.const_field)) End Sub @@ -1990,7 +1990,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Const a As Integer = 1, b As Integer = 2", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As Integer = 1", FeaturesResources.const_field)) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index c2c191621330f..5a6a8a423c7ee 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -29,6 +29,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Iterator End Enum + Public Shared Function GetResource(keyword As String) As String + Select Case keyword + Case "Class" + Return FeaturesResources.class_ + Case "Structure" + Return VBFeaturesResources.structure_ + Case "Module" + Return VBFeaturesResources.module_ + Case "Interface" + Return FeaturesResources.interface_ + Case Else + Throw ExceptionUtilities.UnexpectedValue(keyword) + End Select + End Function + Friend Shared NoSemanticEdits As SemanticEditDescription() = Array.Empty(Of SemanticEditDescription) Friend Overloads Shared Function Diagnostic(rudeEditKind As RudeEditKind, squiggle As String, ParamArray arguments As String()) As RudeEditDiagnosticDescription diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 519ab947a0a8d..976d8b43337a6 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -281,51 +281,6 @@ Option Strict Off #End Region #Region "Attributes" - - Public Sub UpdateAttributes1() - Dim attribute = "Public Class A1Attribute : Inherits System.Attribute : End Class" & vbCrLf & - "Public Class A2Attribute : Inherits System.Attribute : End Class" & vbCrLf - - Dim src1 = attribute & "Class C : End Class" - Dim src2 = attribute & "Class C : End Class" - - Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Class C]@132 -> [Class C]@132") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Class C", FeaturesResources.class_)) - End Sub - - - Public Sub UpdateAttributes2() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Class C]@0 -> [Class C]@0") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Class C", FeaturesResources.class_)) - End Sub - - - Public Sub UpdateAttributes3() - Dim attribute = "Public Class A1Attribute : Inherits System.Attribute : End Class" & vbCrLf & - "Public Class A2Attribute : Inherits System.Attribute : End Class" & vbCrLf - - Dim src1 = attribute & "Module C : End Module" - Dim src2 = attribute & "Module C : End Module" - - Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Module C]@132 -> [Module C]@132") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Module C", VBFeaturesResources.module_)) - End Sub - Public Sub UpdateAttributes_TopLevel1() Dim src1 = "" @@ -472,119 +427,173 @@ Option Strict Off #End Region -#Region "Top-level Classes, Structs, Interfaces, Modules" - - Public Sub ClassInsert() - Dim src1 = "" - Dim src2 = "Class C : End Class" +#Region "Types" + + + + + Public Sub Type_Kind_Update(oldKeyword As String, newKeyword As String) + Dim src1 = oldKeyword & " C : End " & oldKeyword + Dim src2 = newKeyword & " C : End " & newKeyword Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics() + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeKindUpdate, newKeyword & " C")) End Sub - - Public Sub StructInsert() - Dim src1 = "" - Dim src2 = "Structure C : End Structure" - Dim edits = GetTopEdits(src1, src2) + + + Public Sub Type_Modifiers_Accessibility_Change(accessibility As String) - edits.VerifyRudeDiagnostics() - End Sub + Dim src1 = accessibility + " Class C : End Class" + Dim src2 = "Class C : End Class" - - Public Sub PartialInterfaceInsert() - Dim src1 = "" - Dim src2 = "Partial Interface C : End Interface" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemantics(semanticEdits:= - { - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C")) - }) + edits.VerifyEdits( + "Update [" + accessibility + " Class C]@0 -> [Class C]@0") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", FeaturesResources.class_)) End Sub - - Public Sub PartialInterfaceDelete() - Dim src1 = "Partial Interface C : End Interface" - Dim src2 = "" - Dim edits = GetTopEdits(src1, src2) + + + + + + + + + + Public Sub Type_Modifiers_Accessibility_Partial(accessibilityA As String, accessibilityB As String) + + Dim srcA1 = accessibilityA + " Partial Class C : End Class" + Dim srcB1 = "Partial Class C : End Class" + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = accessibilityB + " Partial Class C : End Class" - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.interface_, "C"))) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) End Sub - - Public Sub ModuleInsert() - Dim src1 = "" - Dim src2 = "Module C : End Module" - Dim edits = GetTopEdits(src1, src2) + + + + + + Public Sub Type_Modifiers_Friend_Remove(keyword As String) + Dim src1 = "Friend " & keyword & " C : End " & keyword + Dim src2 = keyword & " C : End " & keyword - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Module C", VBFeaturesResources.module_)) + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() End Sub - - Public Sub PartialModuleInsert() - Dim src1 = "" - Dim src2 = "Partial Module C : End Module" + + + + + + Public Sub Type_Modifiers_Friend_Add(keyword As String) + Dim src1 = keyword & " C : End " & keyword + Dim src2 = "Friend " & keyword & " C : End " & keyword + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() + End Sub + + + + + + Public Sub Type_Modifiers_NestedPrivateInInterface_Remove(keyword As String) + Dim src1 = "Interface C : Private " & keyword & " S : End " & keyword & " : End Interface" + Dim src2 = "Interface C : " & keyword & " S : End " & keyword & " : End Interface" + Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Partial Module C", VBFeaturesResources.module_)) + Diagnostic(RudeEditKind.ChangingAccessibility, keyword + " S", GetResource(keyword))) End Sub - - Public Sub PartialModuleDelete() - Dim src1 = "Partial Module C : End Module" - Dim src2 = "" + + + + + Public Sub Type_Modifiers_NestedPublicInClass_Add(keyword As String) + Dim src1 = "Class C : " & keyword & " S : End " & keyword & " : End Class" + Dim src2 = "Class C : Public " & keyword & " S : End " & keyword & " : End Class" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() + End Sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "C"))) + + + + + Public Sub Type_Modifiers_NestedPublicInInterface_Add(keyword As String) + Dim src1 = "Interface I : " + keyword + " S : End " & keyword & " : End Interface" + Dim src2 = "Interface I : Public " + keyword + " S : End " & keyword & " : End Interface" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() End Sub - Public Sub TypeKindChange1() - Dim src1 = "Class C : End Class" - Dim src2 = "Structure C : End Structure" - Dim edits = GetTopEdits(src1, src2) + Public Sub Type_Attribute_Update_NotSupportedByRuntime1() + Dim attribute = "Public Class A1Attribute : Inherits System.Attribute : End Class" & vbCrLf & + "Public Class A2Attribute : Inherits System.Attribute : End Class" & vbCrLf + + Dim src1 = attribute & "Class C : End Class" + Dim src2 = attribute & "Class C : End Class" + Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Class C : End Class]@0 -> [Structure C : End Structure]@0", - "Update [Class C]@0 -> [Structure C]@0") + "Update [Class C]@132 -> [Class C]@132") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "Structure C", VBFeaturesResources.structure_)) + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Class C", FeaturesResources.class_)) End Sub - - Public Sub TypeKindChange2() - Dim src1 = "Public Module C : End Module" - Dim src2 = "Public Class C : End Class" + + + + + + Public Sub Type_Attribute_Update_NotSupportedByRuntime(keyword As String) + Dim src1 = "" & keyword & " C : End " & keyword + Dim src2 = "" & keyword & " C : End " & keyword Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Public Module C : End Module]@0 -> [Public Class C : End Class]@0", - "Update [Public Module C]@0 -> [Public Class C]@0") + "Update [" & keyword & " C]@0 -> [" & keyword & " C]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "Public Class C", FeaturesResources.class_)) + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, keyword & " C", GetResource(keyword))) End Sub - - Public Sub ModuleModifiersUpdate() - Dim src1 = "Module C : End Module" - Dim src2 = "Partial Module C : End Module" + + + + + + Public Sub Type_PartialModifier_Add(keyword As String) + Dim src1 = keyword & " C : End " & keyword + Dim src2 = "Partial " & keyword & " C : End " & keyword Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Module C]@0 -> [Partial Module C]@0") + "Update [" & keyword & " C]@0 -> [Partial " & keyword & " C]@0") edits.VerifyRudeDiagnostics() End Sub - Public Sub ModuleModifiersUpdate2() + Public Sub Module_PartialModifier_Remove() Dim src1 = "Partial Module C : End Module" Dim src2 = "Module C : End Module" Dim edits = GetTopEdits(src1, src2) @@ -596,53 +605,73 @@ Option Strict Off End Sub - Public Sub InterfaceModifiersUpdate() - Dim src1 = "Public Interface C : End Interface" - Dim src2 = "Interface C : End Interface" + Public Sub ClassInsert() + Dim src1 = "" + Dim src2 = "Class C : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Public Interface C]@0 -> [Interface C]@0") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Interface C", FeaturesResources.interface_)) + edits.VerifyRudeDiagnostics() End Sub - Public Sub InterfaceModifiersUpdate2() - Dim src1 = "Public Interface C : Sub Goo() : End Interface" - Dim src2 = "Interface C : Sub Goo() : End Interface" + Public Sub StructInsert() + Dim src1 = "" + Dim src2 = "Structure C : End Structure" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Public Interface C]@0 -> [Interface C]@0") + edits.VerifyRudeDiagnostics() + End Sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Interface C", FeaturesResources.interface_)) + + Public Sub PartialInterfaceInsert() + Dim src1 = "" + Dim src2 = "Partial Interface C : End Interface" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C")) + }) End Sub - Public Sub InterfaceModifiersUpdate3() - Dim src1 = "Interface C : Sub Goo() : End Interface" - Dim src2 = "Partial Interface C : Sub Goo() : End Interface" + Public Sub PartialInterfaceDelete() + Dim src1 = "Partial Interface C : End Interface" + Dim src2 = "" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Interface C]@0 -> [Partial Interface C]@0") + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.interface_, "C"))) + End Sub - edits.VerifyRudeDiagnostics() + + Public Sub ModuleInsert() + Dim src1 = "" + Dim src2 = "Module C : End Module" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Module C", VBFeaturesResources.module_)) End Sub - Public Sub StructModifiersUpdate() - Dim src1 = "Structure C : End Structure" - Dim src2 = "Public Structure C : End Structure" + Public Sub PartialModuleInsert() + Dim src1 = "" + Dim src2 = "Partial Module C : End Module" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Partial Module C", VBFeaturesResources.module_)) + End Sub + + Public Sub PartialModuleDelete() + Dim src1 = "Partial Module C : End Module" + Dim src2 = "" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Update [Structure C]@0 -> [Public Structure C]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Public Structure C", VBFeaturesResources.structure_)) + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "C"))) End Sub @@ -872,7 +901,11 @@ End Interface {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults(), - DocumentResults() + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("I.VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("I.VirtualFunction")) + }) }) End Sub @@ -1102,7 +1135,8 @@ Delegate Sub D() { SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("M").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember("F")) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("I").GetMember("F")) }) }) End Sub @@ -1177,9 +1211,11 @@ End Interface DocumentResults( semanticEdits:= { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("AbstractMethod")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("VirtualMethod")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("ToString")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("IG")) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("IG")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("I").GetMember("G")) }) }) End Sub @@ -1313,7 +1349,7 @@ End Class End Sub - Public Sub Enum_Modifiers_Update() + Public Sub Enum_Accessibility_Change() Dim src1 = "Public Enum Color : Red = 1 : Blue = 2 : End Enum" Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) @@ -1322,7 +1358,19 @@ End Class "Update [Public Enum Color]@0 -> [Enum Color]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Enum Color", FeaturesResources.enum_)) + Diagnostic(RudeEditKind.ChangingAccessibility, "Enum Color", FeaturesResources.enum_)) + End Sub + + + Public Sub Enum_Accessibility_NoChange() + Dim src1 = "Friend Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Friend Enum Color]@0 -> [Enum Color]@0") + + edits.VerifySemantics() End Sub @@ -1581,7 +1629,7 @@ End Class End Sub - Public Sub Delegates_Update_Modifiers() + Public Sub Delegates_Accessibility_Update() Dim src1 = "Public Delegate Sub D()" Dim src2 = "Private Delegate Sub D()" Dim edits = GetTopEdits(src1, src2) @@ -1590,7 +1638,7 @@ End Class "Update [Public Delegate Sub D()]@0 -> [Private Delegate Sub D()]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Delegate Sub D()", FeaturesResources.delegate_)) + Diagnostic(RudeEditKind.ChangingAccessibility, "Private Delegate Sub D()", FeaturesResources.delegate_)) End Sub @@ -2969,6 +3017,98 @@ End Class" #End Region #Region "Methods" + + + + + + Public Sub Method_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C" & vbCrLf & oldModifiers & "Sub F() : End Sub : End Class" + Dim src2 = "Class C" & vbCrLf & newModifiers & "Sub F() : End Sub : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [" & oldModifiers & "Sub F() : End Sub]@9 -> [" & newModifiers & "Sub F() : End Sub]@9", + "Update [" & oldModifiers & "Sub F()]@9 -> [" & newModifiers & "Sub F()]@9") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "Sub F()", FeaturesResources.method)) + End Sub + + + Public Sub Method_MustOverrideModifier_Update() + Dim src1 = "Class C" & vbCrLf & "MustOverride Sub F() : End Class" + Dim src2 = "Class C" & vbCrLf & "Sub F() : End Sub : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "Sub F()", FeaturesResources.method)) + End Sub + + + + + Public Sub Method_Modifiers_Update_NoImpact(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C " & vbLf & oldModifiers & "Sub F() : End Sub : End Class" + Dim src2 = "Class C " & vbLf & newModifiers & "Sub F() : End Sub : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [" & oldModifiers & "Sub F() : End Sub]@9 -> [" & newModifiers & "Sub F() : End Sub]@9", + "Update [" & oldModifiers & "Sub F()]@9 -> [" & newModifiers & "Sub F()]@9") + + ' Currently, an edit is produced eventhough there is no metadata/IL change. Consider improving. + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"))}) + End Sub + + + Public Sub Method_AsyncModifier_Add() + Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" + Dim src2 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Function F() As Task(Of String) : End Function]@11 -> [Async Function F() As Task(Of String) : End Function]@11", + "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub Method_AsyncModifier_Remove() + Dim src1 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" + Dim src2 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Async Function F() As Task(Of String) : End Function]@11 -> [Function F() As Task(Of String) : End Function]@11", + "Update [Async Function F() As Task(Of String)]@11 -> [Function F() As Task(Of String)]@11") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "Function F()", FeaturesResources.method)) + End Sub Public Sub MethodUpdate1() @@ -3316,35 +3456,6 @@ Imports System.Runtime.InteropServices Diagnostic(RudeEditKind.Renamed, "Sub Bar", FeaturesResources.method)) End Sub - - Public Sub MethodUpdate_AsyncModifier1() - Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" - Dim src2 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" - - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Function F() As Task(Of String) : End Function]@11 -> [Async Function F() As Task(Of String) : End Function]@11", - "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") - - edits.VerifyRudeDiagnostics() - End Sub - - - Public Sub MethodUpdate_AsyncModifier2() - Dim src1 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" - Dim src2 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" - - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Async Function F() As Task(Of String) : End Function]@11 -> [Function F() As Task(Of String) : End Function]@11", - "Update [Async Function F() As Task(Of String)]@11 -> [Function F() As Task(Of String)]@11") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "Function F()", FeaturesResources.method)) - End Sub - Public Sub MethodUpdate_IteratorModifier1() Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" @@ -3943,19 +4054,14 @@ End Class Dim srcA2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" Dim srcB2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" - ' TODO: current - GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.ModifiersUpdate, "Private Sub F()", FeaturesResources.method)) - GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.ModifiersUpdate, "Partial Private Sub F()", FeaturesResources.method)) - - ' TODO: correct - ' EditAndContinueValidation.VerifySemantics( - ' { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - ' - ' { - ' DocumentResults(), - ' DocumentResults( - ' semanticEdits:= { SemanticEdit(SemanticEditKind.Update, c => c.GetMember(Of NamedTypeSymbol)("C").GetMember("F")) }), - ' }); + ' TODO: should be an update edit since the location of the implementation changed + ' semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"))} + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) End Sub @@ -4035,6 +4141,21 @@ End Class #Region "Operators" + + + + Public Sub Operator_Modifiers_Update(oldModifiers As String, newModifiers As String) + Dim src1 = "Class C" & vbCrLf & "Public Shared " & oldModifiers & " Operator CType(d As C) As Integer : End Operator : End Class" + Dim src2 = "Class C" & vbCrLf & "Public Shared " & newModifiers & " Operator CType(d As C) As Integer : End Operator : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits("Update [Public Shared " + oldModifiers + " Operator CType(d As C) As Integer]@9 -> [Public Shared " + newModifiers + " Operator CType(d As C) As Integer]@9") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "Public Shared " & newModifiers & " Operator CType(d As C)", FeaturesResources.operator_)) + End Sub + Public Sub OperatorInsert() Dim src1 = " @@ -4299,7 +4420,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -4411,7 +4532,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, visibility & " Sub New()", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.ChangingAccessibility, visibility & " Sub New()", FeaturesResources.constructor)) End Sub @@ -4535,7 +4656,7 @@ End Class { DocumentResults(), DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.ModifiersUpdate, "Public Sub New()", FeaturesResources.constructor)}) + diagnostics:={Diagnostic(RudeEditKind.ChangingAccessibility, "Public Sub New()", FeaturesResources.constructor)}) }) End Sub @@ -4656,7 +4777,7 @@ End Class - Public Sub InstanceCtor_Partial_VisibilityUpdate(visibility As String) + Public Sub InstanceCtor_Partial_AccessibilityUpdate(visibility As String) If visibility.Length > 0 Then visibility &= " " End If @@ -4672,7 +4793,7 @@ End Class {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.ModifiersUpdate, visibility & "Sub New()", FeaturesResources.constructor)}), + diagnostics:={Diagnostic(RudeEditKind.ChangingAccessibility, visibility & "Sub New()", FeaturesResources.constructor)}), DocumentResults() }) End Sub @@ -5318,12 +5439,35 @@ End Class {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults(), - DocumentResults() + DocumentResults(semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("M.ExternSub"))}) }) End Sub #End Region #Region "Fields" + + + + Public Sub Field_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C : " & oldModifiers & "Dim F As Integer = 0 : End Class" + Dim src2 = "Class C : " & newModifiers & "Dim F As Integer = 0 : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits("Update [" & oldModifiers & "Dim F As Integer = 0]@10 -> [" & newModifiers & "Dim F As Integer = 0]@10") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "F As Integer = 0", FeaturesResources.field)) + End Sub + Public Sub FieldUpdate_Rename1() Dim src1 = "Class C : Dim a As Integer = 0 : End Class" @@ -5633,7 +5777,7 @@ End Class "Update [Dim a As WE]@10 -> [WithEvents a As WE]@10") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "WithEvents a As WE", VBFeaturesResources.WithEvents_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As WE", VBFeaturesResources.WithEvents_field)) End Sub @@ -5646,7 +5790,7 @@ End Class "Update [WithEvents a As WE]@10 -> [Dim a As WE]@10") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Dim a As WE", FeaturesResources.field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As WE", FeaturesResources.field)) End Sub @@ -6120,6 +6264,42 @@ End Class #End Region #Region "Properties" + + + + + + Public Sub Property_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C" & vbLf & oldModifiers & "ReadOnly Property F As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" + Dim src2 = "Class C" & vbLf & newModifiers & "ReadOnly Property F As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits("Update [" & oldModifiers & "ReadOnly Property F As Integer]@8 -> [" & newModifiers & "ReadOnly Property F As Integer]@8") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "ReadOnly Property F", FeaturesResources.property_)) + End Sub + + + Public Sub Property_MustOverrideModifier_Update() + Dim src1 = "Class C" & vbLf & "ReadOnly MustOverride Property F As Integer" & vbLf & "End Class" + Dim src2 = "Class C" & vbLf & "ReadOnly Property F As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "ReadOnly Property F", FeaturesResources.property_)) + End Sub + Public Sub PropertyReorder1() Dim src1 = "Class C : ReadOnly Property P" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : " & @@ -6780,7 +6960,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -6790,7 +6970,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -7563,7 +7743,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Dim x = 0", FeaturesResources.field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "x = 0", FeaturesResources.field)) End Sub @@ -7573,7 +7753,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Const x = 0", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "x = 0", FeaturesResources.const_field)) End Sub @@ -8317,6 +8497,47 @@ End Class #Region "Events" + + + Public Sub Event_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = " +Class C + " & oldModifiers & " Custom Event E As Action + AddHandler(value As Action) + End AddHandler + RemoveHandler(value As Action) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class" + + Dim src2 = " +Class C + " & newModifiers & " Custom Event E As Action + AddHandler(value As Action) + End AddHandler + RemoveHandler(value As Action) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "Event E", FeaturesResources.event_)) + End Sub + Public Sub EventAccessorReorder1() Dim src1 = "Class C : " & diff --git a/src/EditorFeatures/VisualBasicTest/InlineMethod/VisualBasicInlineMethodTests.vb b/src/EditorFeatures/VisualBasicTest/InlineMethod/VisualBasicInlineMethodTests.vb index 8319399c77f65..035d4cdb4b5a6 100644 --- a/src/EditorFeatures/VisualBasicTest/InlineMethod/VisualBasicInlineMethodTests.vb +++ b/src/EditorFeatures/VisualBasicTest/InlineMethod/VisualBasicInlineMethodTests.vb @@ -33,6 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.InlineMethod If diagnosticResults IsNot Nothing Then test.FixedState.ExpectedDiagnostics.AddRange(diagnosticResults) End If + Await test.RunAsync().ConfigureAwait(False) End Function @@ -47,6 +48,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.InlineMethod If diagnnoticResults IsNot Nothing Then test.FixedState.ExpectedDiagnostics.AddRange(diagnnoticResults) End If + Await test.RunAsync().ConfigureAwait(False) End Function diff --git a/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb b/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb index 51d45e8433979..49615cd184a06 100644 --- a/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb +++ b/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb @@ -654,5 +654,60 @@ Imports System Await TestAsync(markup, expected) End Function + + + Public Async Function InvertIfWithoutStatements() As Task + Await TestInRegularAndScriptAsync( +"class C + sub M(x as String) + [||]If x = ""a"" Then + Else + DoSomething() + End If + end sub + + sub DoSomething() + end sub +end class", +"class C + sub M(x as String) + If x IsNot ""a"" Then + DoSomething() + End If + end sub + + sub DoSomething() + end sub +end class") + End Function + + + Public Async Function InvertIfWithOnlyComment() As Task + Await TestInRegularAndScriptAsync( +"class C + sub M(x as String) + [||]If x = ""a"" Then + ' A comment in a blank if statement + Else + DoSomething() + End If + end sub + + sub DoSomething() + end sub +end class", +"class C + sub M(x as String) + If x IsNot ""a"" Then + DoSomething() + Else + ' A comment in a blank if statement + End If + end sub + + sub DoSomething() + end sub +end class") + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb b/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb index 5a85a01c060e6..820341176ecdc 100644 --- a/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb +++ b/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb @@ -708,6 +708,17 @@ End Class", Async Function(w) End Function) End Function + + + Public Async Function TestFindAbstractMethod(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "MustInherit Class A +Public MustOverride Sub M() +End Class", Async Function(w) + Dim item = (Await _aggregator.GetItemsAsync("M")).Single + VerifyNavigateToResultItem(item, "M", "[|M|]()", PatternMatchKind.Exact, NavigateToItemKind.Method, Glyph.MethodPublic, additionalInfo:=String.Format(FeaturesResources.in_0_project_1, "A", "Test")) + End Function) + End Function + diff --git a/src/EditorFeatures/VisualBasicTest/SymbolId/SymbolKeyTestBase.vb b/src/EditorFeatures/VisualBasicTest/SymbolId/SymbolKeyTestBase.vb index 417b098991897..e799f67663a49 100644 --- a/src/EditorFeatures/VisualBasicTest/SymbolId/SymbolKeyTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/SymbolId/SymbolKeyTestBase.vb @@ -118,6 +118,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SymbolId Exit For End Try Next + Return list End Function @@ -206,6 +207,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SymbolId For Each parameter In method.Parameters list.Add(parameter) Next + If localDumper IsNot Nothing Then localDumper.GetLocalSymbols(method, list) End If diff --git a/src/EditorFeatures/VisualBasicTest/Utils.vb b/src/EditorFeatures/VisualBasicTest/Utils.vb index bff5d95709557..fecfbf870587f 100644 --- a/src/EditorFeatures/VisualBasicTest/Utils.vb +++ b/src/EditorFeatures/VisualBasicTest/Utils.vb @@ -54,6 +54,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests If TypeOf (node) Is T Then Return CType(node, T) End If + For Each child In node.ChildNodesAndTokens() If child.IsNode Then Dim foundNode = child.AsNode().FindFirstNodeOfType(Of T)() @@ -62,6 +63,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests End If End If Next + Return Nothing End Function diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs index 88646841d168f..69fa80e320486 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs @@ -363,5 +363,10 @@ internal static void VerifyTypeParameters(Symbol container, ImmutableArray SynthesizedInterfaceMethodImpls() + { + return SpecializedCollections.EmptyEnumerable<(MethodSymbol Body, MethodSymbol Implemented)>(); + } } } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.cs index de45ef9551757..604bfbce2c7b8 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.cs @@ -8,6 +8,7 @@ using Roslyn.Utilities; using System; using System.Collections.Immutable; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { @@ -25,6 +26,11 @@ public SimpleTypeParameterSymbol(Symbol container, int ordinal, string name) _container = container; _ordinal = ordinal; _name = name; + + Debug.Assert(this.TypeParameterKind == (ContainingSymbol is MethodSymbol ? TypeParameterKind.Method : + (ContainingSymbol is NamedTypeSymbol ? TypeParameterKind.Type : + TypeParameterKind.Cref)), + $"Container is {ContainingSymbol?.Kind}, TypeParameterKind is {this.TypeParameterKind}"); } public override string Name @@ -39,7 +45,7 @@ public override int Ordinal public override TypeParameterKind TypeParameterKind { - get { return TypeParameterKind.Type; } + get { return ContainingSymbol is MethodSymbol ? TypeParameterKind.Method : TypeParameterKind.Type; } } public override bool HasConstructorConstraint diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.vb index 6ac0b20075f3a..19b822a4c7a74 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Symbols/SimpleTypeParameterSymbol.vb @@ -21,6 +21,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator _container = container _ordinal = ordinal _name = name + + Debug.Assert(Me.TypeParameterKind = If(TypeOf Me.ContainingSymbol Is MethodSymbol, TypeParameterKind.Method, + If(TypeOf Me.ContainingSymbol Is NamedTypeSymbol, TypeParameterKind.Type, + TypeParameterKind.Cref)), + $"Container is {Me.ContainingSymbol?.Kind}, TypeParameterKind is {Me.TypeParameterKind}") End Sub Public Overrides ReadOnly Property Name As String @@ -37,7 +42,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Public Overrides ReadOnly Property TypeParameterKind As TypeParameterKind Get - Return TypeParameterKind.Type + Return If(TypeOf Me.ContainingSymbol Is MethodSymbol, TypeParameterKind.Method, TypeParameterKind.Type) End Get End Property diff --git a/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs b/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs index 98aa8476b33a0..a79df5f04de87 100644 --- a/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs +++ b/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs @@ -79,6 +79,7 @@ protected override bool CanAddImportForMethod( { node = memberBindingExpr.Name; } + break; case CS1929: var memberAccessName = (node.Parent as MemberAccessExpressionSyntax)?.Name; diff --git a/src/Features/CSharp/Portable/CodeFixes/Nullable/CSharpDeclareAsNullableCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/Nullable/CSharpDeclareAsNullableCodeFixProvider.cs index a0ec4c5eccddd..4c6e11dcb4707 100644 --- a/src/Features/CSharp/Portable/CodeFixes/Nullable/CSharpDeclareAsNullableCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/Nullable/CSharpDeclareAsNullableCodeFixProvider.cs @@ -349,8 +349,10 @@ declarator.Parent is VariableDeclarationSyntax declaration && { return typeArguments[0]; } + break; } + return null; } @@ -363,6 +365,7 @@ parameterSymbol.ContainingSymbol is IMethodSymbol method && { return parameterSyntax.Type; } + return null; } } diff --git a/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensDisplayInfoService.cs b/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensDisplayInfoService.cs index aca40f15c355a..3eca553c17f7a 100644 --- a/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensDisplayInfoService.cs +++ b/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensDisplayInfoService.cs @@ -68,6 +68,7 @@ public SyntaxNode GetDisplayNode(SyntaxNode node) node = structuredTriviaSyntax.ParentTrivia.Token.Parent; continue; } + return null; default: diff --git a/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs index 1d1d5bed174a8..b76a2dce544b5 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs @@ -155,6 +155,7 @@ private static bool CanSimplify( // Don't offer this if there are any errors or ambiguities. return false; } + if (!(lambdaSemanticInfo.Symbol is IMethodSymbol lambdaMethod) || !(invocationSemanticInfo.Symbol is IMethodSymbol invocationMethod)) { return false; diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs index db95740e9ce31..b08aae477befe 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs @@ -69,6 +69,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) { return; } + if (token.Parent.Parent is not AttributeSyntax attributeSyntax || token.Parent is not AttributeArgumentListSyntax attributeArgumentList) { return; diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs index b15a95ab04146..d3bf4298ccfc1 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs @@ -106,6 +106,7 @@ private static string TryRemoveInterfacePrefix(ITypeSymbol type) return name.Substring(1); } } + return type.CreateParameterName(); } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AbstractKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AbstractKeywordRecommender.cs index a062416c33896..4c4cfb7d823b8 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AbstractKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AbstractKeywordRecommender.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders { internal class AbstractKeywordRecommender : AbstractSyntacticSingleKeywordRecommender { - private static readonly ISet s_validMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + private static readonly ISet s_validNonInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.ExternKeyword, SyntaxKind.InternalKeyword, @@ -24,6 +24,18 @@ internal class AbstractKeywordRecommender : AbstractSyntacticSingleKeywordRecomm SyntaxKind.OverrideKeyword, }; + private static readonly ISet s_validInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.OverrideKeyword, + }; + private static readonly ISet s_validTypeModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.InternalKeyword, @@ -44,8 +56,13 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context return context.IsGlobalStatementContext || context.IsMemberDeclarationContext( - validModifiers: s_validMemberModifiers, - validTypeDeclarations: SyntaxKindSet.ClassInterfaceRecordTypeDeclarations, + validModifiers: s_validNonInterfaceMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassRecordTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validInterfaceMemberModifiers, + validTypeDeclarations: SyntaxKindSet.InterfaceOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken) || context.IsTypeDeclarationContext( diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ExplicitKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ExplicitKeywordRecommender.cs index c6ed3cd79e1f0..b7198e35ba18d 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ExplicitKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ExplicitKeywordRecommender.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders { internal class ExplicitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender { - private static readonly ISet s_validMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + private static readonly ISet s_validNonInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.StaticKeyword, SyntaxKind.PublicKeyword, @@ -21,6 +21,14 @@ internal class ExplicitKeywordRecommender : AbstractSyntacticSingleKeywordRecomm SyntaxKind.UnsafeKeyword, }; + private static readonly ISet s_validInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.AbstractKeyword, + SyntaxKind.UnsafeKeyword, + }; + public ExplicitKeywordRecommender() : base(SyntaxKind.ExplicitKeyword) { @@ -28,7 +36,7 @@ public ExplicitKeywordRecommender() protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) { - if (context.IsMemberDeclarationContext(validModifiers: s_validMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructRecordTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + if (context.IsMemberDeclarationContext(validModifiers: s_validNonInterfaceMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructRecordTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) { // operators must be both public and static var modifiers = context.PrecedingModifiers; @@ -37,6 +45,15 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context modifiers.Contains(SyntaxKind.PublicKeyword) && modifiers.Contains(SyntaxKind.StaticKeyword); } + else if (context.IsMemberDeclarationContext(validModifiers: s_validInterfaceMemberModifiers, validTypeDeclarations: SyntaxKindSet.InterfaceOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // operators must be both abstract and static + var modifiers = context.PrecedingModifiers; + + return + modifiers.Contains(SyntaxKind.AbstractKeyword) && + modifiers.Contains(SyntaxKind.StaticKeyword); + } return false; } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ImplicitKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ImplicitKeywordRecommender.cs index 400b2107e28e3..5ba148272eb74 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ImplicitKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ImplicitKeywordRecommender.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders { internal class ImplicitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender { - private static readonly ISet s_validMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + private static readonly ISet s_validNonInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.StaticKeyword, SyntaxKind.PublicKeyword, @@ -21,6 +21,14 @@ internal class ImplicitKeywordRecommender : AbstractSyntacticSingleKeywordRecomm SyntaxKind.UnsafeKeyword, }; + private static readonly ISet s_validInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.AbstractKeyword, + SyntaxKind.UnsafeKeyword, + }; + public ImplicitKeywordRecommender() : base(SyntaxKind.ImplicitKeyword) { @@ -28,7 +36,7 @@ public ImplicitKeywordRecommender() protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) { - if (context.IsMemberDeclarationContext(validModifiers: s_validMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructRecordTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + if (context.IsMemberDeclarationContext(validModifiers: s_validNonInterfaceMemberModifiers, validTypeDeclarations: SyntaxKindSet.ClassStructRecordTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) { // operators must be both public and static var modifiers = context.PrecedingModifiers; @@ -37,6 +45,15 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context modifiers.Contains(SyntaxKind.PublicKeyword) && modifiers.Contains(SyntaxKind.StaticKeyword); } + else if (context.IsMemberDeclarationContext(validModifiers: s_validInterfaceMemberModifiers, validTypeDeclarations: SyntaxKindSet.InterfaceOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + // operators must be both abstract and static + var modifiers = context.PrecedingModifiers; + + return + modifiers.Contains(SyntaxKind.AbstractKeyword) && + modifiers.Contains(SyntaxKind.StaticKeyword); + } return false; } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs index 540f5870c92b2..a339e12eac4c0 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs @@ -24,6 +24,7 @@ internal class RecordKeywordRecommender : AbstractSyntacticSingleKeywordRecommen SyntaxKind.SealedKeyword, SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, + SyntaxKind.ReadOnlyKeyword, }; public RecordKeywordRecommender() diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/SealedKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/SealedKeywordRecommender.cs index c0d5275af6e05..f32f0bf357d36 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/SealedKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/SealedKeywordRecommender.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders { internal class SealedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender { - private static readonly ISet s_validMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + private static readonly ISet s_validNonInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.ExternKeyword, SyntaxKind.InternalKeyword, @@ -24,6 +24,18 @@ internal class SealedKeywordRecommender : AbstractSyntacticSingleKeywordRecommen SyntaxKind.UnsafeKeyword, }; + private static readonly ISet s_validInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.UnsafeKeyword, + }; + private static readonly ISet s_validTypeModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.InternalKeyword, @@ -44,8 +56,13 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context return context.IsGlobalStatementContext || context.IsMemberDeclarationContext( - validModifiers: s_validMemberModifiers, - validTypeDeclarations: SyntaxKindSet.ClassInterfaceRecordTypeDeclarations, + validModifiers: s_validNonInterfaceMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassRecordTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validInterfaceMemberModifiers, + validTypeDeclarations: SyntaxKindSet.InterfaceOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken) || context.IsTypeDeclarationContext( diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StaticKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StaticKeywordRecommender.cs index a9228a2537c46..0df962ed64606 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StaticKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StaticKeywordRecommender.cs @@ -23,7 +23,7 @@ internal class StaticKeywordRecommender : AbstractSyntacticSingleKeywordRecommen SyntaxKind.UnsafeKeyword, }; - private static readonly ISet s_validMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + private static readonly ISet s_validNonInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.AsyncKeyword, SyntaxKind.ExternKeyword, @@ -37,6 +37,22 @@ internal class StaticKeywordRecommender : AbstractSyntacticSingleKeywordRecommen SyntaxKind.VolatileKeyword, }; + private static readonly ISet s_validInterfaceMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AbstractKeyword, + SyntaxKind.AsyncKeyword, + SyntaxKind.ExternKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.NewKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadOnlyKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.UnsafeKeyword, + SyntaxKind.VolatileKeyword, + }; + private static readonly ISet s_validGlobalMemberModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.ExternKeyword, @@ -77,8 +93,13 @@ private static bool IsValidContextForMember(CSharpSyntaxContext context, Cancell return context.SyntaxTree.IsGlobalMemberDeclarationContext(context.Position, s_validGlobalMemberModifiers, cancellationToken) || context.IsMemberDeclarationContext( - validModifiers: s_validMemberModifiers, - validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructRecordTypeDeclarations, + validModifiers: s_validNonInterfaceMemberModifiers, + validTypeDeclarations: SyntaxKindSet.ClassStructRecordTypeDeclarations, + canBePartial: false, + cancellationToken: cancellationToken) || + context.IsMemberDeclarationContext( + validModifiers: s_validInterfaceMemberModifiers, + validTypeDeclarations: SyntaxKindSet.InterfaceOnlyTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken); } diff --git a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs index ae07a09397454..9abe07d9867dd 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs @@ -260,6 +260,7 @@ private bool CanTryConvertToLocalFunction() { return true; } + if (currentNode is ExpressionSyntax || currentNode is ArgumentSyntax || currentNode is ArgumentListSyntax || diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 536701ea63cd6..b55a9e9a39ab9 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1133,6 +1133,7 @@ record.ParameterList is not null && } } } + return false; } @@ -1145,6 +1146,7 @@ internal override bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorPa isFirstAccessor = list.Accessors[0] == declaration; return true; } + return false; } @@ -1179,7 +1181,10 @@ protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference referen Contract.ThrowIfNull(oldNode); Contract.ThrowIfNull(newNode); - // Either old or new property/indexer has an expression body (or both do). + // Certain updates of a property/indexer node affects its accessors. + // Return all affected symbols for these updates. + + // 1) Old or new property/indexer has an expression body: // int this[...] => expr; // int this[...] { get => expr; } // int P => expr; @@ -1196,6 +1201,36 @@ protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference referen return OneOrMany.Create(ImmutableArray.Create((oldSymbol, newSymbol), (oldGetterSymbol, newGetterSymbol))); } + // 2) Property/indexer declarations differ in readonly keyword. + if (oldNode is PropertyDeclarationSyntax oldProperty && newNode is PropertyDeclarationSyntax newProperty && DiffersInReadOnlyModifier(oldProperty.Modifiers, newProperty.Modifiers) || + oldNode is IndexerDeclarationSyntax oldIndexer && newNode is IndexerDeclarationSyntax newIndexer && DiffersInReadOnlyModifier(oldIndexer.Modifiers, newIndexer.Modifiers)) + { + Debug.Assert(oldSymbol is IPropertySymbol); + Debug.Assert(newSymbol is IPropertySymbol); + + var oldPropertySymbol = (IPropertySymbol)oldSymbol; + var newPropertySymbol = (IPropertySymbol)newSymbol; + + using var _ = ArrayBuilder<(ISymbol?, ISymbol?)>.GetInstance(out var builder); + + builder.Add((oldPropertySymbol, newPropertySymbol)); + + if (oldPropertySymbol.GetMethod != null && newPropertySymbol.GetMethod != null && oldPropertySymbol.GetMethod.IsReadOnly != newPropertySymbol.GetMethod.IsReadOnly) + { + builder.Add((oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod)); + } + + if (oldPropertySymbol.SetMethod != null && newPropertySymbol.SetMethod != null && oldPropertySymbol.SetMethod.IsReadOnly != newPropertySymbol.SetMethod.IsReadOnly) + { + builder.Add((oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod)); + } + + return OneOrMany.Create(builder.ToImmutable()); + } + + static bool DiffersInReadOnlyModifier(SyntaxTokenList oldModifiers, SyntaxTokenList newModifiers) + => (oldModifiers.IndexOf(SyntaxKind.ReadOnlyKeyword) >= 0) != (newModifiers.IndexOf(SyntaxKind.ReadOnlyKeyword) >= 0); + break; case EditKind.Delete: @@ -1249,6 +1284,7 @@ protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference referen { parameterSymbol = invokeMethodSymbol.ContainingSymbol; } + return parameterSymbol; } @@ -2298,6 +2334,7 @@ private void ClassifyInsert(SyntaxNode node) { ReportError(RudeEditKind.Insert); } + return; case SyntaxKind.EnumMemberDeclaration: @@ -2318,6 +2355,7 @@ private void ClassifyInsert(SyntaxNode node) { ReportError(RudeEditKind.Insert); } + return; } @@ -2409,6 +2447,7 @@ private void ClassifyDelete(SyntaxNode oldNode) { ReportError(RudeEditKind.Delete); } + return; case SyntaxKind.TypeParameter: @@ -2427,6 +2466,7 @@ private void ClassifyDelete(SyntaxNode oldNode) { ReportError(RudeEditKind.Delete); } + return; } @@ -2564,6 +2604,7 @@ private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) { ReportError(RudeEditKind.Update); } + return; case SyntaxKind.TypeParameterList: @@ -2589,19 +2630,6 @@ private void ClassifyUpdate(NamespaceDeclarationSyntax oldNode, NamespaceDeclara private void ClassifyUpdate(TypeDeclarationSyntax oldNode, TypeDeclarationSyntax newNode) { - if (oldNode.Kind() != newNode.Kind()) - { - ReportError(RudeEditKind.TypeKindUpdate); - return; - } - - // Allow partial keyword to be added or removed. - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.PartialKeyword, ignore2: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier)) { ReportError(RudeEditKind.Renamed); @@ -2622,12 +2650,6 @@ private void ClassifyUpdate(EnumDeclarationSyntax oldNode, EnumDeclarationSyntax return; } - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.BaseList, newNode.BaseList)) { ReportError(RudeEditKind.EnumUnderlyingTypeUpdate); @@ -2640,12 +2662,6 @@ private void ClassifyUpdate(EnumDeclarationSyntax oldNode, EnumDeclarationSyntax private void ClassifyUpdate(DelegateDeclarationSyntax oldNode, DelegateDeclarationSyntax newNode) { - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType)) { ReportError(RudeEditKind.TypeUpdate); @@ -2665,12 +2681,6 @@ private void ClassifyUpdate(BaseFieldDeclarationSyntax oldNode, BaseFieldDeclara ReportError(RudeEditKind.FieldKindUpdate); return; } - - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } } private void ClassifyUpdate(VariableDeclarationSyntax oldNode, VariableDeclarationSyntax newNode) @@ -2729,13 +2739,6 @@ private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSy return; } - // Ignore async keyword when matching modifiers. Async checks are done in ComputeBodyMatch. - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.AsyncKeyword, ignore2: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType)) { ReportError(RudeEditKind.TypeUpdate); @@ -2757,15 +2760,9 @@ private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSy private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, ConversionOperatorDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.ImplicitOrExplicitKeyword, newNode.ImplicitOrExplicitKeyword)) { - ReportError(RudeEditKind.Renamed); + ReportError(RudeEditKind.ModifiersUpdate); return; } @@ -2784,12 +2781,6 @@ private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, Convers private void ClassifyUpdate(OperatorDeclarationSyntax oldNode, OperatorDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.OperatorToken, newNode.OperatorToken)) { ReportError(RudeEditKind.Renamed); @@ -2811,12 +2802,6 @@ private void ClassifyUpdate(OperatorDeclarationSyntax oldNode, OperatorDeclarati private void ClassifyUpdate(AccessorDeclarationSyntax oldNode, AccessorDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (oldNode.Kind() != newNode.Kind()) { ReportError(RudeEditKind.AccessorKindUpdate); @@ -2852,12 +2837,6 @@ private void ClassifyUpdate(EnumMemberDeclarationSyntax oldNode, EnumMemberDecla private void ClassifyUpdate(ConstructorDeclarationSyntax oldNode, ConstructorDeclarationSyntax newNode) { - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - ClassifyMethodBodyRudeUpdate( (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, @@ -2876,12 +2855,6 @@ private void ClassifyUpdate(DestructorDeclarationSyntax oldNode, DestructorDecla private void ClassifyUpdate(PropertyDeclarationSyntax oldNode, PropertyDeclarationSyntax newNode) { - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type)) { ReportError(RudeEditKind.TypeUpdate); @@ -2936,12 +2909,6 @@ private void ClassifyUpdate(PropertyDeclarationSyntax oldNode, PropertyDeclarati private void ClassifyUpdate(IndexerDeclarationSyntax oldNode, IndexerDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type)) { ReportError(RudeEditKind.TypeUpdate); @@ -2954,16 +2921,17 @@ private void ClassifyUpdate(IndexerDeclarationSyntax oldNode, IndexerDeclaration return; } - Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody)); - - var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList); - var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList); + if (SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody)) + { + var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList); + var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList); - ClassifyMethodBodyRudeUpdate( - oldBody, - newBody, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); + ClassifyMethodBodyRudeUpdate( + oldBody, + newBody, + containingMethod: null, + containingType: (TypeDeclarationSyntax?)newNode.Parent); + } } private void ClassifyUpdate(TypeParameterSyntax oldNode, TypeParameterSyntax newNode) diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 8a4aa880d6630..84384afef4d08 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -626,6 +626,7 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, SyntaxNode? node, out bo { return Label.AttributeList; } + break; case SyntaxKind.Attribute: @@ -635,6 +636,7 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, SyntaxNode? node, out bo isLeaf = true; return Label.Attribute; } + break; } @@ -1265,6 +1267,7 @@ private static void GetLocalNames(ExpressionSyntax expression, ref List RemoveInitializedDeclarationAndRet { return statements; } + if (!(statements.ElementAtOrDefault(0) is LocalDeclarationStatementSyntax declaration) || !(statements.ElementAtOrDefault(1) is ReturnStatementSyntax returnStatement)) { return statements; diff --git a/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs b/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs index d038b32461384..093da30d31508 100644 --- a/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs +++ b/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs @@ -109,6 +109,7 @@ public async Task> GetFormattingChangesAsync( var inferredIndentationService = document.Project.Solution.Workspace.Services.GetRequiredService(); options = await inferredIndentationService.GetDocumentOptionsWithInferredIndentationAsync(document, explicitFormat: true, cancellationToken: cancellationToken).ConfigureAwait(false); } + return Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), document.Project.Solution.Workspace, options, cancellationToken).ToImmutableArray(); diff --git a/src/Features/CSharp/Portable/ImplementInterface/AbstractChangeImplementionCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ImplementInterface/AbstractChangeImplementionCodeRefactoringProvider.cs index e28eb764dea36..a8456785be94a 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/AbstractChangeImplementionCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/AbstractChangeImplementionCodeRefactoringProvider.cs @@ -171,6 +171,7 @@ private static int TotalCount(MemberImplementationMap dictionary) { result += values.Count; } + return result; } diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs index c38665713bb6b..89c738968cc5f 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; using System.Composition; @@ -42,7 +40,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var span = context.Span; var cancellationToken = context.CancellationToken; - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(span.Start); if (!token.Span.IntersectsWith(span)) @@ -50,7 +48,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - var service = document.GetLanguageService(); + var service = document.GetRequiredLanguageService(); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var actions = token.Parent.GetAncestorsOrThis() diff --git a/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs b/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs index acb3bf5e62e40..cc423d436e8a3 100644 --- a/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs +++ b/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs @@ -42,6 +42,7 @@ public CSharpInlineTypeHintsService() if (IsValidType(type)) return CreateTypeHint(type, displayAllOverride, forImplicitVariableTypes, variableDeclaration.Type, variableDeclaration.Variables[0].Identifier); } + if (node is DeclarationExpressionSyntax { Type: { IsVar: true } } declarationExpression) { var type = semanticModel.GetTypeInfo(declarationExpression.Type, cancellationToken).Type; diff --git a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs index 5bfa5c6d16756..90060995990ee 100644 --- a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; using System.Collections.Generic; using System.Composition; using System.Diagnostics.CodeAnalysis; @@ -41,7 +40,7 @@ protected override SyntaxNode GetCondition(IfStatementSyntax ifNode) => ifNode.Condition; protected override StatementRange GetIfBodyStatementRange(IfStatementSyntax ifNode) - => new StatementRange(ifNode.Statement, ifNode.Statement); + => new(ifNode.Statement, ifNode.Statement); protected override bool IsStatementContainer(SyntaxNode node) => node.IsKind(SyntaxKind.Block, SyntaxKind.SwitchSection); @@ -52,7 +51,7 @@ protected override bool IsNoOpSyntaxNode(SyntaxNode node) protected override bool IsExecutableStatement(SyntaxNode node) => node is StatementSyntax; - protected override StatementSyntax GetNextStatement(StatementSyntax node) + protected override StatementSyntax? GetNextStatement(StatementSyntax node) => node.GetNextStatement(); protected override StatementSyntax GetIfBody(IfStatementSyntax ifNode) @@ -62,7 +61,7 @@ protected override StatementSyntax GetEmptyEmbeddedStatement() => SyntaxFactory.Block(); protected override StatementSyntax GetElseBody(IfStatementSyntax ifNode) - => ifNode.Else.Statement; + => ifNode.Else?.Statement ?? throw new InvalidOperationException(); protected override bool CanControlFlowOut(SyntaxNode node) { @@ -154,7 +153,7 @@ protected override IfStatementSyntax UpdateIf( IfStatementSyntax ifNode, SyntaxNode condition, StatementSyntax trueStatement, - StatementSyntax falseStatementOpt = null) + StatementSyntax? falseStatementOpt = null) { var isSingleLine = sourceText.AreOnSameLine(ifNode.GetFirstToken(), ifNode.GetLastToken()); if (isSingleLine && falseStatementOpt != null) @@ -175,7 +174,7 @@ protected override IfStatementSyntax UpdateIf( ? SyntaxFactory.Block(trueStatement) : trueStatement); - if (falseStatementOpt != null) + if (ShouldKeepFalse(ifNode, falseStatementOpt)) { var elseClause = updatedIf.Else != null ? updatedIf.Else.WithStatement(falseStatementOpt) @@ -183,6 +182,10 @@ protected override IfStatementSyntax UpdateIf( updatedIf = updatedIf.WithElse(elseClause); } + else + { + updatedIf = updatedIf.WithElse(null); + } // If this is multiline, format things after we swap around the if/else. Because // of all the different types of rewriting, we may need indentation fixed up and @@ -193,6 +196,50 @@ protected override IfStatementSyntax UpdateIf( : updatedIf.WithAdditionalAnnotations(Formatter.Annotation); } + private static bool ShouldKeepFalse(IfStatementSyntax originalIfStatement, [NotNullWhen(returnValue: true)] StatementSyntax? falseStatement) + { + // The original false statement doesn't exist at all + // then no need to consider keeping it around + if (falseStatement is null) + { + return false; + } + + if (falseStatement is BlockSyntax falseBlock) + { + // Block false syntax with some statements included. + // If there are no statements, it's an empty + // block and we can get rid of it. + if (falseBlock.Statements.Any()) + { + return true; + } + + // If the stateements for the else don't pass, we still need to check + // if there are comments from the original if that should be included. + // If so, pass them along to be copied to the new else block + return originalIfStatement.Statement is BlockSyntax block + && BlockHasComment(block); + } + + // The statement is not expected to have children, so we know it's fine + // to consider this something that needs to be included. Such as + // a return statement, or other similar things for single line if/else. + return true; + + static bool BlockHasComment(BlockSyntax block) + { + return block.CloseBraceToken.LeadingTrivia.Any(HasCommentTrivia) + || block.OpenBraceToken.TrailingTrivia.Any(HasCommentTrivia); + } + + static bool HasCommentTrivia(SyntaxTrivia trivia) + { + return trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) + || trivia.IsKind(SyntaxKind.SingleLineCommentTrivia); + } + } + protected override SyntaxNode WithStatements(SyntaxNode node, IEnumerable statements) { switch (node) diff --git a/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeRefactoringProvider.cs index 233566094f888..2132e02303889 100644 --- a/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeRefactoringProvider.cs @@ -27,7 +27,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var (document, textSpan, cancellationToken) = context; var syntaxTree = (await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false))!; - if (!MakeLocalFunctionStaticHelper.IsStaticLocalFunctionSupported(syntaxTree)) + if (!MakeLocalFunctionStaticHelper.IsStaticLocalFunctionSupported(((CSharpParseOptions)syntaxTree.Options).LanguageVersion)) { return; } diff --git a/src/Features/CSharp/Portable/MakeLocalFunctionStatic/PassInCapturedVariablesAsArgumentsCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeLocalFunctionStatic/PassInCapturedVariablesAsArgumentsCodeFixProvider.cs index f1d0a0b23f14c..d2d9b23211f3c 100644 --- a/src/Features/CSharp/Portable/MakeLocalFunctionStatic/PassInCapturedVariablesAsArgumentsCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeLocalFunctionStatic/PassInCapturedVariablesAsArgumentsCodeFixProvider.cs @@ -78,7 +78,7 @@ private static async Task WrapFixAsync( // Even when the language version doesn't support staic local function, the compiler will still // generate this error. So we need to check to make sure we don't provide incorrect fix. - if (!MakeLocalFunctionStaticHelper.IsStaticLocalFunctionSupported(root.SyntaxTree)) + if (!MakeLocalFunctionStaticHelper.IsStaticLocalFunctionSupported(((CSharpParseOptions)root.SyntaxTree.Options).LanguageVersion)) { return; } diff --git a/src/Features/CSharp/Portable/MakeTypeAbstract/CSharpMakeTypeAbstractCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeTypeAbstract/CSharpMakeTypeAbstractCodeFixProvider.cs index e39ddd3c9fd3b..9a581c6b3ab05 100644 --- a/src/Features/CSharp/Portable/MakeTypeAbstract/CSharpMakeTypeAbstractCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeTypeAbstract/CSharpMakeTypeAbstractCodeFixProvider.cs @@ -36,6 +36,7 @@ protected override bool IsValidRefactoringContext(SyntaxNode? node, [NotNullWhen typeDeclaration = null; return false; } + break; case AccessorDeclarationSyntax accessor: @@ -44,6 +45,7 @@ protected override bool IsValidRefactoringContext(SyntaxNode? node, [NotNullWhen typeDeclaration = null; return false; } + break; default: diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs index c2c3121c882b6..4e7f37db9d9f4 100644 --- a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs @@ -276,6 +276,7 @@ private static MethodDeclarationSyntax GetGetMethodWorker( .WithExpressionBody(getAccessorDeclaration.ExpressionBody) .WithSemicolonToken(getAccessorDeclaration.SemicolonToken); } + if (getAccessorDeclaration?.Body != null) { return methodDeclaration.WithBody(getAccessorDeclaration.Body) diff --git a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs index b79e74ef15c73..30c60d39781ed 100644 --- a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs @@ -43,10 +43,12 @@ public CSharpUpgradeProjectCodeFixProvider() "CS8511", // error CS8511: An expression of type 'T' cannot be handled by a pattern of type ''. Please use language version 'preview' or greater to match an open type with a constant pattern. "CS8627", // error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type unless language version '{0}' or greater is used. Consider changing the language version or adding a 'class', 'struct', or type constraint. "CS8652", // error CS8652: The feature '' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + "CS8773", // error CS8773: Feature is not available in C# 9.0. Please use language version X or greater. "CS8703", // error CS8703: The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. "CS8706", // error CS8706: '{0}' cannot implement interface member '{1}' in type '{2}' because feature '{3}' is not available in C# {4}. Please use language version '{5}' or greater. "CS8904", // error CS8904: Invalid variance: The type parameter 'T1' must be contravariantly valid on 'I2.M1(T1)' unless language version 'preview' or greater is used. 'T1' is covariant. "CS8912", // error CS8912: Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + "CS8704", // error CS8704: 'Test1' does not implement interface member 'I1.M1()'. 'Test1.M1()' cannot implicitly implement a non-public member in C# 9.0. Please use language version 'preview' or greater. }); public override string UpgradeThisProjectResource => CSharpFeaturesResources.Upgrade_this_project_to_csharp_language_version_0; diff --git a/src/Features/Core/Portable/CodeCleanup/DiagnosticSet.cs b/src/Features/Core/Portable/CodeCleanup/DiagnosticSet.cs index 79843492db80d..29fd7b4dbd97e 100644 --- a/src/Features/Core/Portable/CodeCleanup/DiagnosticSet.cs +++ b/src/Features/Core/Portable/CodeCleanup/DiagnosticSet.cs @@ -21,4 +21,4 @@ public DiagnosticSet(string description, params string[] diagnosticIds) DiagnosticIds = ImmutableArray.Create(diagnosticIds); } } -} \ No newline at end of file +} diff --git a/src/Features/Core/Portable/CodeRefactorings/AddAwait/AbstractAddAwaitCodeRefactoringProvider.cs b/src/Features/Core/Portable/CodeRefactorings/AddAwait/AbstractAddAwaitCodeRefactoringProvider.cs index 32139f9b2d9e9..b072c25983350 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AddAwait/AbstractAddAwaitCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AddAwait/AbstractAddAwaitCodeRefactoringProvider.cs @@ -46,7 +46,7 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex for (var i = expressions.Length - 1; i >= 0; i--) { var expression = expressions[i]; - if (IsValidAwaitableExpression(expression, model, syntaxFacts)) + if (IsValidAwaitableExpression(model, syntaxFacts, expression, cancellationToken)) { context.RegisterRefactoring( new MyCodeAction( @@ -63,24 +63,28 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex } } - private static bool IsValidAwaitableExpression(SyntaxNode invocation, SemanticModel model, ISyntaxFactsService syntaxFacts) + private static bool IsValidAwaitableExpression( + SemanticModel model, ISyntaxFactsService syntaxFacts, SyntaxNode node, CancellationToken cancellationToken) { - if (syntaxFacts.IsExpressionOfInvocationExpression(invocation.Parent)) + if (syntaxFacts.IsExpressionOfInvocationExpression(node.Parent)) { // Do not offer fix on `MethodAsync()$$.ConfigureAwait()` // Do offer fix on `MethodAsync()$$.Invalid()` - if (!model.GetTypeInfo(invocation.GetRequiredParent().GetRequiredParent()).Type.IsErrorType()) + if (!model.GetTypeInfo(node.GetRequiredParent().GetRequiredParent(), cancellationToken).Type.IsErrorType()) return false; } - if (syntaxFacts.IsExpressionOfAwaitExpression(invocation)) + if (syntaxFacts.IsExpressionOfAwaitExpression(node)) return false; - var type = model.GetTypeInfo(invocation).Type; - if (type?.IsAwaitableNonDynamic(model, invocation.SpanStart) == true) - return true; + // if we're on an actual type symbol itself (like literally `Task`) we don't want to offer to add await. + // we only want to add for actual expressions whose type is awaitable, not on the awaitable type itself. + var symbol = model.GetSymbolInfo(node, cancellationToken).GetAnySymbol(); + if (symbol is ITypeSymbol) + return false; - return false; + var type = model.GetTypeInfo(node, cancellationToken).Type; + return type?.IsAwaitableNonDynamic(model, node.SpanStart) == true; } private static Task AddAwaitAsync( diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs index 139c4efddf2ec..56077d699a069 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs @@ -13,7 +13,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -107,6 +109,11 @@ private ImmutableArray CreateActions(State state, CancellationToken var manyTypes = MultipleTopLevelTypeDeclarationInSourceDocument(state.SemanticDocument.Root); var isNestedType = IsNestedType(state.TypeNode); + var syntaxFacts = state.SemanticDocument.Document.GetRequiredLanguageService(); + var isClassNextToGlobalStatements = manyTypes + ? false + : ClassNextToGlobalStatements(state.SemanticDocument.Root, syntaxFacts); + var suggestedFileNames = GetSuggestedFileNames( state.TypeNode, isNestedType, @@ -120,7 +127,9 @@ private ImmutableArray CreateActions(State state, CancellationToken // case 2: This is a nested type, offer to move to new file. // case 3: If there is a single type decl in current file, *do not* offer move to new file, // rename actions are sufficient in this case. - if (manyTypes || isNestedType) + // case 4: If there are top level statements(Global statements) offer to move even + // in cases where there are only one class in the file. + if (manyTypes || isNestedType || isClassNextToGlobalStatements) { foreach (var fileName in suggestedFileNames) { @@ -152,6 +161,9 @@ private ImmutableArray CreateActions(State state, CancellationToken return actions.ToImmutable(); } + private static bool ClassNextToGlobalStatements(SyntaxNode root, ISyntaxFactsService syntaxFacts) + => syntaxFacts.ContainsGlobalStatement(root); + private CodeAction GetCodeAction(State state, string fileName, MoveTypeOperationKind operationKind) => new MoveTypeCodeAction((TService)this, state, operationKind, fileName); diff --git a/src/Features/Core/Portable/Common/GlyphExtensions.cs b/src/Features/Core/Portable/Common/GlyphExtensions.cs index 0d38774159f19..c011f551c16b8 100644 --- a/src/Features/Core/Portable/Common/GlyphExtensions.cs +++ b/src/Features/Core/Portable/Common/GlyphExtensions.cs @@ -208,7 +208,7 @@ private static Glyph GetGlyph(string tag, ImmutableArray allTags) return Glyph.None; } - private static Accessibility GetAccessibility(ImmutableArray tags) + public static Accessibility GetAccessibility(ImmutableArray tags) { foreach (var tag in tags) { diff --git a/src/Features/Core/Portable/Common/TaggedTextStyle.cs b/src/Features/Core/Portable/Common/TaggedTextStyle.cs index cf7a756e86aad..4440b13bb3434 100644 --- a/src/Features/Core/Portable/Common/TaggedTextStyle.cs +++ b/src/Features/Core/Portable/Common/TaggedTextStyle.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; namespace Microsoft.CodeAnalysis @@ -13,12 +11,14 @@ internal enum TaggedTextStyle { None = 0, - Strong = 0x1, + Strong = 1 << 0, + + Emphasis = 1 << 1, - Emphasis = 0x2, + Underline = 1 << 2, - Underline = 0x4, + Code = 1 << 3, - Code = 0x8, + PreserveWhitespace = 1 << 4, } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionFilterReason.cs b/src/Features/Core/Portable/Completion/CompletionFilterReason.cs similarity index 100% rename from src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionFilterReason.cs rename to src/Features/Core/Portable/Completion/CompletionFilterReason.cs diff --git a/src/Features/Core/Portable/Completion/CompletionHelper.cs b/src/Features/Core/Portable/Completion/CompletionHelper.cs index 4b4b57cef0455..f20f17ac7ab38 100644 --- a/src/Features/Core/Portable/Completion/CompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/CompletionHelper.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Tags; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion { @@ -358,5 +359,109 @@ private static int CompareExpandedItem(CompletionItem item1, PatternMatch match1 public static string ConcatNamespace(string? containingNamespace, string name) => string.IsNullOrEmpty(containingNamespace) ? name : containingNamespace + "." + name; + + internal static bool TryCreateMatchResult( + CompletionHelper completionHelper, + CompletionItem item, + T editorCompletionItem, + string filterText, + CompletionTriggerKind initialTriggerKind, + CompletionFilterReason filterReason, + ImmutableArray recentItems, + bool includeMatchSpans, + int currentIndex, + out MatchResult matchResult) + { + // Get the match of the given completion item for the pattern provided so far. + // A completion item is checked against the pattern by see if it's + // CompletionItem.FilterText matches the item. That way, the pattern it checked + // against terms like "IList" and not IList<>. + // Note that the check on filter text length is purely for efficiency, we should + // get the same result with or without it. + var patternMatch = filterText.Length > 0 + ? completionHelper.GetMatch(item.FilterText, filterText, includeMatchSpans, CultureInfo.CurrentCulture) + : null; + + var matchedFilterText = MatchesFilterText( + item, + filterText, + initialTriggerKind, + filterReason, + recentItems, + patternMatch); + + if (matchedFilterText || KeepAllItemsInTheList(initialTriggerKind, filterText)) + { + matchResult = new MatchResult( + item, editorCompletionItem, matchedFilterText: matchedFilterText, + patternMatch: patternMatch, currentIndex); + + return true; + } + + matchResult = default; + return false; + + static bool MatchesFilterText( + CompletionItem item, + string filterText, + CompletionTriggerKind initialTriggerKind, + CompletionFilterReason filterReason, + ImmutableArray recentItems, + PatternMatch? patternMatch) + { + // For the deletion we bake in the core logic for how matching should work. + // This way deletion feels the same across all languages that opt into deletion + // as a completion trigger. + + // Specifically, to avoid being too aggressive when matching an item during + // completion, we require that the current filter text be a prefix of the + // item in the list. + if (filterReason == CompletionFilterReason.Deletion && + initialTriggerKind == CompletionTriggerKind.Deletion) + { + return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; + } + + // If the user hasn't typed anything, and this item was preselected, or was in the + // MRU list, then we definitely want to include it. + if (filterText.Length == 0) + { + if (item.Rules.MatchPriority > MatchPriority.Default) + { + return true; + } + + if (!recentItems.IsDefault && GetRecentItemIndex(recentItems, item) <= 0) + { + return true; + } + } + + // Otherwise, the item matches filter text if a pattern match is returned. + return patternMatch != null; + } + + static int GetRecentItemIndex(ImmutableArray recentItems, CompletionItem item) + { + var index = recentItems.IndexOf(item.FilterText); + return -index; + } + + // If the item didn't match the filter text, we still keep it in the list + // if one of two things is true: + // 1. The user has typed nothing or only typed a single character. In this case they might + // have just typed the character to get completion. Filtering out items + // here is not desirable. + // + // 2. They brought up completion with ctrl-j or through deletion. In these + // cases we just always keep all the items in the list. + static bool KeepAllItemsInTheList(CompletionTriggerKind initialTriggerKind, string filterText) + { + return filterText.Length <= 1 || + initialTriggerKind == CompletionTriggerKind.Invoke || + initialTriggerKind == CompletionTriggerKind.Deletion; + } + } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs b/src/Features/Core/Portable/Completion/MatchResult.cs similarity index 74% rename from src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs rename to src/Features/Core/Portable/Completion/MatchResult.cs index b13c657baa436..de60e1348c684 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs +++ b/src/Features/Core/Portable/Completion/MatchResult.cs @@ -5,26 +5,26 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.VisualStudio.Text; using Roslyn.Utilities; using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem; -using VSCompletionItem = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem; -namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion +namespace Microsoft.CodeAnalysis.Completion { - internal readonly struct MatchResult + internal readonly struct MatchResult { public readonly RoslynCompletionItem RoslynCompletionItem; public readonly bool MatchedFilterText; - public readonly ImmutableArray HighlightedSpans; // In certain cases, there'd be no match but we'd still set `MatchedFilterText` to true, // e.g. when the item is in MRU list. Therefore making this nullable. public readonly PatternMatch? PatternMatch; - public readonly VSCompletionItem VSCompletionItem; + /// + /// The actual editor completion item associated with this + /// In VS for example, this is the associated VS async completion item. + /// + public readonly TEditorCompletionItem EditorCompletionItem; // We want to preserve the original alphabetical order for items with same pattern match score, // but `ArrayBuilder.Sort` we currently use isn't stable. So we have to add a monotonically increasing @@ -32,26 +32,27 @@ internal readonly struct MatchResult private readonly int _indexInOriginalSortedOrder; public MatchResult( - RoslynCompletionItem roslynCompletionItem, VSCompletionItem vsCompletionItem, - bool matchedFilterText, PatternMatch? patternMatch, int index, - ImmutableArray highlightedSpans) + RoslynCompletionItem roslynCompletionItem, + TEditorCompletionItem editorCompletionItem, + bool matchedFilterText, + PatternMatch? patternMatch, + int index) { RoslynCompletionItem = roslynCompletionItem; + EditorCompletionItem = editorCompletionItem; MatchedFilterText = matchedFilterText; PatternMatch = patternMatch; - VSCompletionItem = vsCompletionItem; _indexInOriginalSortedOrder = index; - HighlightedSpans = highlightedSpans; } - public static IComparer SortingComparer => FilterResultSortingComparer.Instance; + public static IComparer> SortingComparer => FilterResultSortingComparer.Instance; - private class FilterResultSortingComparer : IComparer + private class FilterResultSortingComparer : IComparer> { public static FilterResultSortingComparer Instance { get; } = new FilterResultSortingComparer(); // This comparison is used for sorting items in the completion list for the original sorting. - public int Compare(MatchResult x, MatchResult y) + public int Compare(MatchResult x, MatchResult y) { var matchX = x.PatternMatch; var matchY = y.PatternMatch; @@ -80,11 +81,11 @@ public int Compare(MatchResult x, MatchResult y) } // This comparison is used in the deletion/backspace scenario for selecting best elements. - public int CompareTo(MatchResult other, string filterText) + public int CompareTo(MatchResult other, string filterText) => ComparerWithState.CompareTo(this, other, filterText, s_comparers); - private static readonly ImmutableArray> s_comparers = - ImmutableArray.Create>( + private static readonly ImmutableArray, string, IComparable>> s_comparers = + ImmutableArray.Create, string, IComparable>>( // Prefer the item that matches a longer prefix of the filter text. (f, s) => f.RoslynCompletionItem.FilterText.GetCaseInsensitivePrefixLength(s), // If there are "Abc" vs "abc", we should prefer the case typed by user. diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index 3d84f2452cce8..7e4e98d481e4c 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -53,7 +53,7 @@ public static CompletionItem Create( { // We don't need arity to recover symbol if we already have SymbolKeyData or it's 0. // (but it still needed below to decide whether to show generic suffix) - builder.Add(TypeAritySuffixName, AbstractDeclaredSymbolInfoFactoryService.GetMetadataAritySuffix(arity)); + builder.Add(TypeAritySuffixName, ArityUtilities.GetMetadataAritySuffix(arity)); } properties = builder.ToImmutableDictionaryAndFree(); diff --git a/src/Features/Core/Portable/Completion/Utilities.cs b/src/Features/Core/Portable/Completion/Utilities.cs index e4f9b9b65e3d8..409e6e8a8e305 100644 --- a/src/Features/Core/Portable/Completion/Utilities.cs +++ b/src/Features/Core/Portable/Completion/Utilities.cs @@ -38,5 +38,12 @@ public static TextChange Collapse(SourceText newText, ImmutableArray return new TextChange(totalOldSpan, newText.ToString(totalNewSpan)); } + + // This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items. + // We expect that Editor will introduce this support and we will get rid of relying on the "★" then. + // We check both the display text and the display text prefix to account for IntelliCode item providers + // that may be using the prefix to include the ★. + internal static bool IsPreferredItem(this CompletionItem completionItem) + => completionItem.DisplayText.StartsWith("★") || completionItem.DisplayTextPrefix.StartsWith("★"); } } diff --git a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticModeService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticModeServiceFactory.cs similarity index 64% rename from src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticModeService.cs rename to src/Features/Core/Portable/Diagnostics/DefaultDiagnosticModeServiceFactory.cs index f547f08b76b5a..72e287dcb8965 100644 --- a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticModeService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticModeServiceFactory.cs @@ -5,32 +5,31 @@ using System; using System.Collections.Generic; using System.Composition; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics +namespace Microsoft.CodeAnalysis.Diagnostics { - [ExportWorkspaceServiceFactory(typeof(IDiagnosticModeService), ServiceLayer.Host), Shared] - internal class VisualStudioDiagnosticModeServiceFactory : IWorkspaceServiceFactory + [ExportWorkspaceServiceFactory(typeof(IDiagnosticModeService), ServiceLayer.Default), Shared] + internal class DefaultDiagnosticModeServiceFactory : IWorkspaceServiceFactory { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioDiagnosticModeServiceFactory() + public DefaultDiagnosticModeServiceFactory() { } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new VisualStudioDiagnosticModeService(workspaceServices.Workspace); + => new DefaultDiagnosticModeService(workspaceServices.Workspace); - private class VisualStudioDiagnosticModeService : IDiagnosticModeService + private class DefaultDiagnosticModeService : IDiagnosticModeService { private readonly Workspace _workspace; private readonly Dictionary, Lazy> _optionToMode = new(); - public VisualStudioDiagnosticModeService(Workspace workspace) + public DefaultDiagnosticModeService(Workspace workspace) { _workspace = workspace; } @@ -65,11 +64,26 @@ private DiagnosticMode ComputeDiagnosticMode(Option2 option) if (inCodeSpacesServer) return DiagnosticMode.Pull; + var diagnosticModeOption = _workspace.Options.GetOption(option); + + // If the workspace diagnostic mode is set to Default, defer to the feature flag service. + if (diagnosticModeOption == DiagnosticMode.Default) + { + return GetDiagnosticModeFromFeatureFlag(); + } + // Otherwise, defer to the workspace+option to determine what mode we're in. - return _workspace.Options.GetOption(option); + return diagnosticModeOption; + } + + private DiagnosticMode GetDiagnosticModeFromFeatureFlag() + { + var featureFlagService = _workspace.Services.GetRequiredService(); + var isPullDiagnosticExperimentEnabled = featureFlagService.IsExperimentEnabled(WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag); + return isPullDiagnosticExperimentEnabled ? DiagnosticMode.Pull : DiagnosticMode.Push; } - private bool IsInCodeSpacesServer() + private static bool IsInCodeSpacesServer() { // hack until there is an officially supported free-threaded synchronous platform API to ask this question. return Environment.GetEnvironmentVariable("VisualStudioServerMode") == "1"; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 3d398a1b76fc5..a483dbe3b8649 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -188,6 +188,52 @@ public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) return stateSets.ToImmutable(); } + /// + /// Determines if any of the state sets in match a specified predicate. + /// + /// + /// This method avoids the performance overhead of calling for the + /// specific case where the result is only used for testing if any element meets certain conditions. + /// + public bool HasAnyHostStateSet(Func match, TArg arg) + { + foreach (var (_, hostStateSet) in _hostAnalyzerStateMap) + { + foreach (var stateSet in hostStateSet.OrderedStateSets) + { + if (match(stateSet, arg)) + return true; + } + } + + return false; + } + + /// + /// Determines if any of the state sets in for a specific project + /// match a specified predicate. + /// + /// + /// This method avoids the performance overhead of calling for the + /// specific case where the result is only used for testing if any element meets certain conditions. + /// + /// Note that host state sets (i.e. ones retured by are not tested + /// by this method. + /// + public bool HasAnyProjectStateSet(ProjectId projectId, Func match, TArg arg) + { + if (_projectAnalyzerStateMap.TryGetValue(projectId, out var entry)) + { + foreach (var (_, stateSet) in entry.StateSetMap) + { + if (match(stateSet, arg)) + return true; + } + } + + return false; + } + public bool OnProjectRemoved(IEnumerable stateSets, ProjectId projectId) { var removed = false; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 1366c6d094351..dfe9aaba981bf 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -64,17 +63,11 @@ public DiagnosticIncrementalAnalyzer( internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache; + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54400", Constraint = "Avoid calling GetAllHostStateSets on this hot path.")] public bool ContainsDiagnostics(ProjectId projectId) { - foreach (var stateSet in _stateManager.GetStateSets(projectId)) - { - if (stateSet.ContainsAnyDocumentOrProjectDiagnostics(projectId)) - { - return true; - } - } - - return false; + return _stateManager.HasAnyHostStateSet(static (stateSet, arg) => stateSet.ContainsAnyDocumentOrProjectDiagnostics(arg), projectId) + || _stateManager.HasAnyProjectStateSet(projectId, static (stateSet, arg) => stateSet.ContainsAnyDocumentOrProjectDiagnostics(arg), projectId); } public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticModeService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticModeService.cs index 35b6175278cb6..473a6a3d6cfaa 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticModeService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticModeService.cs @@ -2,10 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Composition; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Diagnostics @@ -19,30 +16,6 @@ internal interface IDiagnosticModeService : IWorkspaceService DiagnosticMode GetDiagnosticMode(Option2 diagnosticMode); } - [ExportWorkspaceServiceFactory(typeof(IDiagnosticModeService)), Shared] - internal class DefaultDiagnosticModeServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultDiagnosticModeServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new DefaultDiagnosticModeService(workspaceServices.Workspace); - - private class DefaultDiagnosticModeService : IDiagnosticModeService - { - private readonly Workspace _workspace; - - public DefaultDiagnosticModeService(Workspace workspace) - => _workspace = workspace; - - public DiagnosticMode GetDiagnosticMode(Option2 diagnosticMode) - => _workspace.Options.GetOption(diagnosticMode); - } - } - internal static class DiagnosticModeExtensions { public static DiagnosticMode GetDiagnosticMode(this Workspace workspace, Option2 option) diff --git a/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs b/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs index 3714fea91d5ef..03c637fc424b9 100644 --- a/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs +++ b/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Xml; using System.Xml.Linq; @@ -102,10 +103,14 @@ public void AppendSingleSpace() public void AppendString(string s) { EmitPendingChars(); - - Builder.Add(new TaggedText(TextTags.Text, s, Style, NavigationTarget.target, NavigationTarget.hint)); + Builder.Add(new TaggedText(TextTags.Text, NormalizeLineEndings(s), Style, NavigationTarget.target, NavigationTarget.hint)); _anyNonWhitespaceSinceLastPara = true; + + // XText.Value returns a string with `\n` as the line endings, causing + // the end result to have mixed line-endings. So normalize everything to `\r\n`. + // https://www.w3.org/TR/xml/#sec-line-ends + static string NormalizeLineEndings(string input) => input.Replace("\n", "\r\n"); } public void AppendParts(IEnumerable parts) @@ -305,8 +310,9 @@ public ImmutableArray Format(string rawXmlText, ISymbol symbol, Sema private static void AppendTextFromNode(FormatterState state, XNode node, Compilation compilation) { - if (node.NodeType == XmlNodeType.Text) + if (node.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) { + // cast is safe since XCData inherits XText AppendTextFromTextNode(state, (XText)node); } @@ -354,13 +360,16 @@ DocumentationCommentXmlNames.SeeAlsoElementName or return; } - else if (name is DocumentationCommentXmlNames.CElementName - or DocumentationCommentXmlNames.CodeElementName - or "tt") + else if (name is DocumentationCommentXmlNames.CElementName or "tt") { needPopStyle = true; state.PushStyle(TaggedTextStyle.Code); } + else if (name is DocumentationCommentXmlNames.CodeElementName) + { + needPopStyle = true; + state.PushStyle(TaggedTextStyle.Code | TaggedTextStyle.PreserveWhitespace); + } else if (name is "em" or "i") { needPopStyle = true; @@ -550,6 +559,13 @@ private static string TrimCrefPrefix(string value) private static void AppendTextFromTextNode(FormatterState state, XText element) { var rawText = element.Value; + if ((state.Style & TaggedTextStyle.PreserveWhitespace) == TaggedTextStyle.PreserveWhitespace) + { + // Don't normalize code from middle. Only trim leading/trailing new lines. + state.AppendString(rawText.Trim('\n')); + return; + } + var builder = new StringBuilder(rawText.Length); // Normalize the whitespace. diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 81e68f05df5c7..2e20695da1d9a 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2485,7 +2485,8 @@ private async Task> AnalyzeSemanticsAsync( // their accessors below. continue; } - else if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst)) + + if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst)) { // Defer a constructor edit to cover the property initializer changing DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); @@ -2505,10 +2506,11 @@ private async Task> AnalyzeSemanticsAsync( } } - // can't change visibility: + // The new symbol is implicitly declared and thus has implied accessibility that needs to be the same + // as the accessibility of the deleted explicit symbol. if (newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility) { - ReportDeletedMemberRudeEdit(diagnostics, editScript, oldDeclaration, oldSymbol, RudeEditKind.ChangingVisibility); + ReportDeletedMemberRudeEdit(diagnostics, editScript, oldDeclaration, oldSymbol, RudeEditKind.ChangingAccessibility); continue; } @@ -2594,16 +2596,6 @@ private async Task> AnalyzeSemanticsAsync( if (oldSymbol.IsImplicitlyDeclared) { - // Replace implicit declaration with an explicit one with a different visibility is a rude edit. - if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility) - { - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.ChangingVisibility, - GetDiagnosticSpan(newDeclaration, edit.Kind), - arguments: new[] { GetDisplayName(newDeclaration, edit.Kind) })); - - continue; - } - // If a user explicitly implements a member of a record then we want to issue an update, not an insert. if (oldSymbol.DeclaringSyntaxReferences.Length == 1) { @@ -2666,25 +2658,14 @@ private async Task> AnalyzeSemanticsAsync( // if it changed in any way that's not allowed. ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol); - // If a node has been inserted but neither old nor new has a body, we can stop processing. - // The exception to this is explicitly implemented properties that implement positional parameters of - // records, as even not having an initializer is an "edit", since the compiler generated property would have - // had one. - var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration); - var oldBody = TryGetDeclarationBody(oldDeclaration); - var newBody = TryGetDeclarationBody(newDeclaration); - if (oldBody == null && newBody == null && !isRecordPrimaryConstructorParameter) - { - continue; - } - if (oldBody != null) { // The old symbol's declaration syntax may be located in a different document than the old version of the current document. var oldSyntaxDocument = oldProject.Solution.GetRequiredDocument(oldDeclaration.SyntaxTree); var oldSyntaxModel = await oldSyntaxDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var oldSyntaxText = await oldSyntaxDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newBody = TryGetDeclarationBody(newDeclaration); // Skip analysis of active statements. We already report rude edit for removal of code containing // active statements in the old declaration and don't currently support moving active statements. @@ -2711,16 +2692,21 @@ private async Task> AnalyzeSemanticsAsync( // If a constructor changes from including initializers to not including initializers // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. var isNewConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); - if (isNewConstructorWithMemberInitializers || - IsDeclarationWithInitializer(oldDeclaration) || - IsDeclarationWithInitializer(newDeclaration) || - isRecordPrimaryConstructorParameter) + var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration); + var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration); + + if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer || isRecordPrimaryConstructorParameter) { if (isNewConstructorWithMemberInitializers) { processedSymbols.Remove(newSymbol); } + if (isDeclarationWithInitializer) + { + AnalyzeSymbolUpdate(oldSymbol, newSymbol, newDeclaration, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + } + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); // Don't add a separate semantic edit. @@ -2888,18 +2874,19 @@ private async Task> AnalyzeSemanticsAsync( // If a constructor changes from including initializers to not including initializers // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); + var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration); - if (isConstructorWithMemberInitializers || - IsDeclarationWithInitializer(oldDeclaration) || - IsDeclarationWithInitializer(newDeclaration)) + if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) { if (isConstructorWithMemberInitializers) { processedSymbols.Remove(newSymbol); } - // Need to check for attribute rude edits for fields and properties - AnalyzeCustomAttributes(oldSymbol, newSymbol, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + if (isDeclarationWithInitializer) + { + AnalyzeSymbolUpdate(oldSymbol, newSymbol, newDeclaration, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + } DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); @@ -2919,7 +2906,9 @@ private async Task> AnalyzeSemanticsAsync( if (editKind == SemanticEditKind.Update) { - AnalyzeCustomAttributes(oldSymbol, newSymbol, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + Contract.ThrowIfNull(oldSymbol); + + AnalyzeSymbolUpdate(oldSymbol, newSymbol, newDeclaration, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); // The only update to the type itself that's supported is an addition or removal of the partial modifier, // which does not have impact on the emitted type metadata. @@ -2928,9 +2917,9 @@ private async Task> AnalyzeSemanticsAsync( continue; } - // The field/property itself is being updated. Currently we do not allow any modifiers to be updated. Attribute - // updates will have been handled already - if (newSymbol is IFieldSymbol or IPropertySymbol) + // The field/property/event itself is being updated. Currently we do not allow any modifiers to be updated. + // Attribute updates will have been handled already. + if (newSymbol is IFieldSymbol or IPropertySymbol or IEventSymbol) { continue; } @@ -2988,7 +2977,9 @@ private async Task> AnalyzeSemanticsAsync( Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration)); var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); - if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newDeclaration)) + var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration); + + if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) { // TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration); @@ -3020,6 +3011,7 @@ private async Task> AnalyzeSemanticsAsync( oldModel, oldCompilation, processedSymbols, + capabilities, isStatic: false, semanticEdits, diagnostics, @@ -3034,6 +3026,7 @@ private async Task> AnalyzeSemanticsAsync( oldModel, oldCompilation, processedSymbols, + capabilities, isStatic: true, semanticEdits, diagnostics, @@ -3062,7 +3055,111 @@ private async Task> AnalyzeSemanticsAsync( } } - private void AnalyzeCustomAttributes(ISymbol? oldSymbol, ISymbol newSymbol, EditAndContinueCapabilities capabilities, ArrayBuilder diagnostics, ArrayBuilder? semanticEdits, Func? syntaxMap, CancellationToken cancellationToken) + private void ReportUpdatedSymbolDeclarationRudeEdits(ArrayBuilder diagnostics, ISymbol oldSymbol, ISymbol newSymbol, SyntaxNode newDeclaration) + { + var rudeEdit = RudeEditKind.None; + + if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility) + { + rudeEdit = RudeEditKind.ChangingAccessibility; + } + + if (oldSymbol.IsStatic != newSymbol.IsStatic || + oldSymbol.IsVirtual != newSymbol.IsVirtual || + oldSymbol.IsAbstract != newSymbol.IsAbstract || + oldSymbol.IsOverride != newSymbol.IsOverride) + { + // Do not report for accessors as the error will be reported on their associated symbol. + if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null }) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + + if (oldSymbol is IFieldSymbol oldField && newSymbol is IFieldSymbol newField) + { + if (oldField.IsConst != newField.IsConst || + oldField.IsReadOnly != newField.IsReadOnly || + oldField.IsVolatile != newField.IsVolatile) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + else if (oldSymbol is IMethodSymbol oldMethod && newSymbol is IMethodSymbol newMethod) + { + if (oldMethod.IsReadOnly != newMethod.IsReadOnly) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + else if (oldSymbol is INamedTypeSymbol oldType && newSymbol is INamedTypeSymbol newType) + { + if (oldType.TypeKind != newType.TypeKind || + oldType.IsRecord != newType.IsRecord) // TODO: https://github.com/dotnet/roslyn/issues/51874 + { + rudeEdit = RudeEditKind.TypeKindUpdate; + } + else if (oldType.IsRefLikeType != newType.IsRefLikeType || + oldType.IsReadOnly != newType.IsReadOnly) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + else if (oldSymbol is IEventSymbol { AddMethod: not null, RemoveMethod: not null } oldEvent && + newSymbol is IEventSymbol { AddMethod: not null, RemoveMethod: not null } newEvent) + { + // "readonly" modifier can only be applied on the event itself, not on its accessors. + if (oldEvent.AddMethod.IsReadOnly != newEvent.AddMethod.IsReadOnly || + oldEvent.RemoveMethod.IsReadOnly != newEvent.RemoveMethod.IsReadOnly) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + + // Do not report modifier update if type kind changed. + if (rudeEdit == RudeEditKind.None && oldSymbol.IsSealed != newSymbol.IsSealed) + { + // Do not report for accessors as the error will be reported on their associated symbol. + if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null }) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + + if (rudeEdit != RudeEditKind.None) + { + var arguments = rudeEdit switch + { + RudeEditKind.TypeKindUpdate => Array.Empty(), + _ => new[] { GetDisplayName(newDeclaration, EditKind.Update) } + }; + + diagnostics.Add(new RudeEditDiagnostic(rudeEdit, GetDiagnosticSpan(newDeclaration, EditKind.Update), arguments: arguments)); + } + } + + private void AnalyzeSymbolUpdate( + ISymbol oldSymbol, + ISymbol newSymbol, + SyntaxNode? newDeclaration, + EditAndContinueCapabilities capabilities, + ArrayBuilder diagnostics, + ArrayBuilder? semanticEdits, + Func? syntaxMap, + CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(newSymbol.IsImplicitlyDeclared == newDeclaration is null); + + AnalyzeCustomAttributes(oldSymbol, newSymbol, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + + // We might be updating an explicit old declaration to an implicit new declaration. + if (newDeclaration != null) + { + ReportUpdatedSymbolDeclarationRudeEdits(diagnostics, oldSymbol, newSymbol, newDeclaration); + } + } + + private void AnalyzeCustomAttributes(ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilities capabilities, ArrayBuilder diagnostics, ArrayBuilder? semanticEdits, Func? syntaxMap, CancellationToken cancellationToken) { var needsEdit = false; @@ -3077,26 +3174,30 @@ private void AnalyzeCustomAttributes(ISymbol? oldSymbol, ISymbol newSymbol, Edit } else if (newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null } newType) { - var oldType = oldSymbol as INamedTypeSymbol; + if (oldSymbol is not INamedTypeSymbol { DelegateInvokeMethod: not null } oldType) + { + return; + } + // If this is a delegate with attributes on its return type for example, they are found on the DelegateInvokeMethod - AnalyzeCustomAttributes(oldType?.DelegateInvokeMethod, newType.DelegateInvokeMethod, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + AnalyzeCustomAttributes(oldType.DelegateInvokeMethod, newType.DelegateInvokeMethod, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); } foreach (var parameter in newSymbol.GetParameters()) { - var oldParameter = oldSymbol?.GetParameters().FirstOrDefault(p => p.Name.Equals(parameter.Name)); + var oldParameter = oldSymbol.GetParameters().FirstOrDefault(p => p.Name.Equals(parameter.Name)); needsEdit |= HasCustomAttributeChanges(oldParameter?.GetAttributes(), parameter.GetAttributes(), parameter, capabilities, diagnostics); } foreach (var typeParam in newSymbol.GetTypeParameters()) { - var oldParameter = oldSymbol?.GetTypeParameters().FirstOrDefault(p => p.Name.Equals(typeParam.Name)); + var oldParameter = oldSymbol.GetTypeParameters().FirstOrDefault(p => p.Name.Equals(typeParam.Name)); needsEdit |= HasCustomAttributeChanges(oldParameter?.GetAttributes(), typeParam.GetAttributes(), typeParam, capabilities, diagnostics); } // This is the only case we care about whether to issue an edit or not, because this is the only case where types have their attributes checked // and types are the only things that would otherwise not have edits reported. - needsEdit |= HasCustomAttributeChanges(oldSymbol?.GetAttributes(), newSymbol.GetAttributes(), newSymbol, capabilities, diagnostics); + needsEdit |= HasCustomAttributeChanges(oldSymbol.GetAttributes(), newSymbol.GetAttributes(), newSymbol, capabilities, diagnostics); // If we don't need to add an edit, then we're done if (!needsEdit || semanticEdits is null) @@ -3105,7 +3206,7 @@ private void AnalyzeCustomAttributes(ISymbol? oldSymbol, ISymbol newSymbol, Edit } // Most symbol types will automatically have an edit added, so we just need to handle a few - if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol) + if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol or IEventSymbol) { var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null, partialType: null)); @@ -3179,6 +3280,7 @@ static void FindChangedAttributes(ImmutableArray? oldAttributes, } } } + return null; } @@ -3533,6 +3635,7 @@ private void AddConstructorEdits( SemanticModel? oldModel, Compilation oldCompilation, IReadOnlySet processedSymbols, + EditAndContinueCapabilities capabilities, bool isStatic, [Out] ArrayBuilder semanticEdits, [Out] ArrayBuilder diagnostics, @@ -3581,49 +3684,45 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) var syntaxMapToUse = aggregateSyntaxMap; + SyntaxNode? newDeclaration = null; ISymbol? oldCtor; if (!newCtor.IsImplicitlyDeclared) { // Constructors have to have a single declaration syntax, they can't be partial - var newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); + newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); + + // Implicit record constructors are represented by the record declaration itself. + // https://github.com/dotnet/roslyn/issues/54403 + var isPrimaryRecordConstructor = IsRecordDeclaration(newDeclaration); - // Compiler generated constructors of records are not implicitly declared, since they - // points to the actual record declaration. We want to skip these checks because we can't - // reason about initializers for them. - if (!IsRecordDeclaration(newDeclaration)) + // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously + // or was not edited. In either case we should not produce a semantic edit for it. + if (!isPrimaryRecordConstructor && !IsConstructorWithMemberInitializers(newDeclaration)) { - // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously - // or was not edited. In either case we should not produce a semantic edit for it. - if (!IsConstructorWithMemberInitializers(newDeclaration)) - { - continue; - } + continue; + } - // If no initializer updates were made in the type we only need to produce semantic edits for constructors - // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. - // If changes were made to initializers or constructors of a partial type in another document they will be merged - // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will - // be reported in the document they were made in. - if (!anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) - { - continue; - } + // If no initializer updates were made in the type we only need to produce semantic edits for constructors + // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. + // If changes were made to initializers or constructors of a partial type in another document they will be merged + // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will + // be reported in the document they were made in. + if (!isPrimaryRecordConstructor && !anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) + { + continue; } // To avoid costly SymbolKey resolution we first try to match the constructor in the current document // and special case parameter-less constructor. - // In the case of records, newDeclaration will point to the record declaration, and hence this - // actually finds the old record declaration, but that is actually sufficient for our needs, as all - // we're using it for is detecting an update, and any changes to the standard record constructors must - // be an update by definition. - if (topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) + // In the case of records, newDeclaration will point to the record declaration, take the slow path. + if (!isPrimaryRecordConstructor && topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) { Contract.ThrowIfNull(oldModel); oldCtor = oldModel.GetDeclaredSymbol(oldDeclaration, cancellationToken); - Contract.ThrowIfNull(oldCtor); + Contract.ThrowIfFalse(oldCtor is IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor }); } - else if (newCtor.Parameters.Length == 0) + else if (!isPrimaryRecordConstructor && newCtor.Parameters.Length == 0) { oldCtor = TryGetParameterlessConstructor(oldType, isStatic); } @@ -3661,7 +3760,7 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) // When explicitly implementing the copy constructor of a record the parameter name must match for symbol matching to work // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 if (oldCtor != null && - !IsRecordDeclaration(newDeclaration) && + !isPrimaryRecordConstructor && oldCtor.DeclaringSyntaxReferences.Length == 0 && newCtor.Parameters.Length == 1 && newType.IsRecord && @@ -3718,6 +3817,8 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) if (oldCtor != null) { + AnalyzeSymbolUpdate(oldCtor, newCtor, newDeclaration, capabilities, diagnostics, semanticEdits: null, syntaxMap: null, cancellationToken); + semanticEdits.Add(new SemanticEditInfo( SemanticEditKind.Update, newCtorKey, diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs index 8c72c5ce9ce97..f3ca5140cfa6c 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs @@ -180,8 +180,11 @@ void AddStatement(LinePositionSpan unmappedLineSpan, ActiveStatement activeState } var hasAnyLineDirectives = false; - foreach (var (unmappedSection, mappedSection) in oldTree.GetLineMappings(cancellationToken)) + foreach (var lineMapping in oldTree.GetLineMappings(cancellationToken)) { + var unmappedSection = lineMapping.Span; + var mappedSection = lineMapping.MappedSpan; + hasAnyLineDirectives = true; var targetPath = mappedSection.HasMappedPath ? mappedSection.Path : oldTree.FilePath; diff --git a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs index 42cba97deb7bb..089e2da7d7b03 100644 --- a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs +++ b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs @@ -328,7 +328,7 @@ public Task OnSourceFileUpdatedAsync(Document document, CancellationToken cancel var debugInfoReader = debugInfoReaderProvider.CreateEditAndContinueMethodDebugInfoReader(); - var documentTasks = project.State.DocumentStates.States.Select(async documentState => + var documentTasks = project.State.DocumentStates.States.Values.Select(async documentState => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 32c79d77a897f..1029bfd4204c2 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -109,7 +109,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.StackAllocUpdate, nameof(FeaturesResources.Modifying_0_which_contains_the_stackalloc_operator_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ExperimentalFeaturesEnabled, nameof(FeaturesResources.Modifying_source_with_experimental_language_features_enabled_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.AwaitStatementUpdate, nameof(FeaturesResources.Updating_a_complex_statement_containing_an_await_expression_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.ChangingVisibility, nameof(FeaturesResources.Changing_visibility_of_0_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ChangingAccessibility, nameof(FeaturesResources.Changing_visibility_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.CapturingVariable, nameof(FeaturesResources.Capturing_variable_0_that_hasn_t_been_captured_before_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.NotCapturingVariable, nameof(FeaturesResources.Ceasing_to_capture_variable_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.DeletingCapturedVariable, nameof(FeaturesResources.Deleting_captured_variable_0_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs index 06459d0085077..a2970db04abf6 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs @@ -97,7 +97,7 @@ public void OnSourceFileUpdated(Document document) foreach (var debuggingSession in GetActiveDebuggingSessions()) { // fire and forget - _ = Task.Run(() => debuggingSession.OnSourceFileUpdatedAsync(document)); + _ = Task.Run(() => debuggingSession.OnSourceFileUpdatedAsync(document)).ReportNonFatalErrorAsync(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index b58a6cb1d55ad..2511afe8fbf26 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -740,7 +740,6 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution { Contract.ThrowIfNull(emitResult.Baseline); - // TODO: Pass these to ManagedModuleUpdate in the new debugger contracts API var updatedMethodTokens = emitResult.UpdatedMethods.SelectAsArray(h => MetadataTokens.GetToken(h)); var updatedTypeTokens = emitResult.UpdatedTypes.SelectAsArray(h => MetadataTokens.GetToken(h)); @@ -762,7 +761,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution pdbStream.ToImmutableArray(), projectChanges.LineChanges, updatedMethodTokens, - updatedTypes: ImmutableArray.Empty, + updatedTypeTokens, activeStatementsInUpdatedMethods, exceptionRegionUpdates)); diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 98c571f9c6802..bd26ddb8f6edd 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -62,7 +62,7 @@ internal enum RudeEditKind : ushort ExperimentalFeaturesEnabled = 45, AwaitStatementUpdate = 46, - ChangingVisibility = 47, + ChangingAccessibility = 47, CapturingVariable = 48, NotCapturingVariable = 49, diff --git a/src/Features/Core/Portable/ExternalAccess/IntelliCode/Api/IntelliCodeCompletionOptions.cs b/src/Features/Core/Portable/ExternalAccess/IntelliCode/Api/IntelliCodeCompletionOptions.cs new file mode 100644 index 0000000000000..fe59bf815df21 --- /dev/null +++ b/src/Features/Core/Portable/ExternalAccess/IntelliCode/Api/IntelliCodeCompletionOptions.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.ExternalAccess.IntelliCode.Api +{ + internal static class IntelliCodeCompletionOptions + { + public static PerLanguageOption TriggerOnTyping { get; } = (PerLanguageOption)CompletionOptions.TriggerOnTyping; + + public static PerLanguageOption TriggerOnTypingLetters { get; } = (PerLanguageOption)CompletionOptions.TriggerOnTypingLetters2; + } +} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs index c4dd30b3a6994..fdc128d2512ab 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs @@ -19,7 +19,12 @@ internal sealed class UnitTestingHotReloadService { private sealed class DebuggerService : IManagedEditAndContinueDebuggerService { - public static readonly DebuggerService Instance = new(); + private readonly ImmutableArray _capabilities; + + public DebuggerService(ImmutableArray capabilities) + { + _capabilities = capabilities; + } public Task> GetActiveStatementsAsync(CancellationToken cancellationToken) => Task.FromResult(ImmutableArray.Empty); @@ -27,9 +32,8 @@ public Task> GetActiveStatements public Task GetAvailabilityAsync(Guid module, CancellationToken cancellationToken) => Task.FromResult(new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.Available)); - // TODO: get capabilities from the runtime public Task> GetCapabilitiesAsync(CancellationToken cancellationToken) - => Task.FromResult(ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition")); + => Task.FromResult(_capabilities); public Task PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) => Task.CompletedTask; @@ -64,24 +68,27 @@ public UnitTestingHotReloadService(HostWorkspaceServices services) /// Starts the watcher. /// /// Solution that represents sources that match the built binaries on disk. + /// Array of capabilities retrieved from the runtime to dictate supported rude edits. /// Cancellation token. - public async Task StartSessionAsync(Solution solution, CancellationToken cancellationToken) + public async Task StartSessionAsync(Solution solution, ImmutableArray capabilities, CancellationToken cancellationToken) { - var newSessionId = await _encService.StartDebuggingSessionAsync(solution, DebuggerService.Instance, captureMatchingDocuments: true, reportDiagnostics: false, cancellationToken).ConfigureAwait(false); + var newSessionId = await _encService.StartDebuggingSessionAsync(solution, new DebuggerService(capabilities), captureMatchingDocuments: true, reportDiagnostics: false, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(_sessionId == default, "Session already started"); _sessionId = newSessionId; } /// - /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call or - /// the one passed to for the first invocation. + /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call + /// where was `true` or the one passed to + /// for the first invocation. /// /// Solution snapshot. + /// commits changes if true, discards if false /// Cancellation token. /// /// Updates (one for each changed project) and Rude Edit diagnostics. Does not include syntax or semantic diagnostics. /// - public async Task<(ImmutableArray updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync(Solution solution, CancellationToken cancellationToken) + public async Task<(ImmutableArray updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync(Solution solution, bool commitUpdates, CancellationToken cancellationToken) { var sessionId = _sessionId; Contract.ThrowIfFalse(sessionId != default, "Session has not started"); @@ -90,7 +97,14 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell if (results.ModuleUpdates.Status == ManagedModuleUpdateStatus.Ready) { - _encService.CommitSolutionUpdate(sessionId, out _); + if (commitUpdates) + { + _encService.CommitSolutionUpdate(sessionId, out _); + } + else + { + _encService.DiscardSolutionUpdate(sessionId); + } } var updates = results.ModuleUpdates.Updates.SelectAsArray( diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index c8148eb835f3e..1badd4a005f6f 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -41,13 +41,15 @@ public readonly struct Update public readonly ImmutableArray ILDelta; public readonly ImmutableArray MetadataDelta; public readonly ImmutableArray PdbDelta; + public readonly ImmutableArray UpdatedTypes; - public Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta) + public Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta, ImmutableArray updatedTypes) { ModuleId = moduleId; ILDelta = ilDelta; MetadataDelta = metadataDelta; PdbDelta = pdbDelta; + UpdatedTypes = updatedTypes; } } @@ -94,7 +96,7 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell } var updates = results.ModuleUpdates.Updates.SelectAsArray( - update => new Update(update.Module, update.ILDelta, update.MetadataDelta, update.PdbDelta)); + update => new Update(update.Module, update.ILDelta, update.MetadataDelta, update.PdbDelta, update.UpdatedTypes)); var diagnostics = await results.GetAllDiagnosticsAsync(solution, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/FindUsages/DefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.cs index 95640414d3ea6..c12672789b0ea 100644 --- a/src/Features/Core/Portable/FindUsages/DefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.cs @@ -117,8 +117,8 @@ protected DefinitionItem( ImmutableArray originationParts, ImmutableArray sourceSpans, ImmutableDictionary properties, - bool displayIfNoReferences) : - this( + bool displayIfNoReferences) + : this( tags, displayParts, nameDisplayParts, diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs index 7d1ba6154ece9..8d308dfc95efa 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs @@ -309,7 +309,9 @@ private ISymbol GenerateMember( var generateInvisibleMember = GenerateInvisibleMember(member, memberName); memberName = generateInvisibleMember ? member.Name : memberName; - var generateAbstractly = !generateInvisibleMember && Abstractly; + // The language doesn't allow static abstract implementations of interface methods. i.e, + // Only interface member is declared abstract static, but implementation should be only static. + var generateAbstractly = !member.IsStatic && !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 @@ -393,7 +395,7 @@ private ISymbol GenerateMember( ImplementTypePropertyGenerationBehavior propertyGenerationBehavior) { var factory = Document.GetLanguageService(); - var modifiers = new DeclarationModifiers(isAbstract: generateAbstractly, isNew: addNew, isUnsafe: addUnsafe); + var modifiers = new DeclarationModifiers(isStatic: member.IsStatic, isAbstract: generateAbstractly, isNew: addNew, isUnsafe: addUnsafe); var useExplicitInterfaceSymbol = generateInvisibly || !Service.CanImplementImplicitly; var accessibility = member.Name == memberName || generateAbstractly diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.cs index fb8f63adbdd11..7ec7b25926695 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction_Method.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editing; @@ -25,7 +23,7 @@ private ISymbol GenerateMethod( bool useExplicitInterfaceSymbol, string memberName) { - var syntaxFacts = Document.GetLanguageService(); + var syntaxFacts = Document.GetRequiredLanguageService(); var updatedMethod = method.EnsureNonConflictingNames(State.ClassOrStructType, syntaxFacts); diff --git a/src/Features/Core/Portable/InheritanceMargin/InheritanceRelationship.cs b/src/Features/Core/Portable/InheritanceMargin/InheritanceRelationship.cs index d5133fc5e8dd0..7715f132eb745 100644 --- a/src/Features/Core/Portable/InheritanceMargin/InheritanceRelationship.cs +++ b/src/Features/Core/Portable/InheritanceMargin/InheritanceRelationship.cs @@ -18,34 +18,48 @@ internal enum InheritanceRelationship None = 0, /// - /// Indicate the target is implementing the member. It would be shown as I↑. + /// Implented interfaces for class or struct. Shown as I↑ /// - Implementing = 1, + ImplementedInterface = 1, /// - /// Indicate the target is implemented by the member. It would be shown as I↓. + /// Base type for class or struct. Shown as O↑ /// - Implemented = 2, + BaseType = 2, /// - /// Indicate the target is overriding the member. It would be shown as O↑. + /// Derived type for class or struct. Shown as O↓ /// - Overriding = 4, + DerivedType = 4, /// - /// Indicate the target is overridden by the member. It would be shown as O↓. + /// Inherited interface for interface. Shown as I↑ /// - Overridden = 8, + InheritedInterface = 8, /// - /// A compound value for indicating there are multiple targets both implementing and overriding the member. + /// Implementing class, struct and interface for interface. Shown as I↓ /// - ImplementingOverriding = InheritanceRelationship.Implementing | InheritanceRelationship.Overriding, + ImplementingType = 16, /// - /// A compound value for indicating there are multiple targets both implementing the member and overriden by the member. + /// Implemented member for member in class or structure. Shown as I↑ /// - ImplementingOverridden = InheritanceRelationship.Implementing | InheritanceRelationship.Overridden + ImplementedMember = 32, + /// + /// Overridden member for member in class or structure. Shown as O↑ + /// + OverriddenMember = 64, + + /// + /// Overrrding member for member in class or structure. Shown as O↓ + /// + OverridingMember = 128, + + /// + /// Implmenting member for member in interface. Shown as I↓ + /// + ImplementingMember = 256 } } diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs index 15f6ea0cd5e6c..42d8243ae3363 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs @@ -69,6 +69,13 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex return; } + // Need to special case for highlighting of method types because they are also "contained" within a method, + // but it does not make sense to introduce a parameter in that case. + if (syntaxFacts.IsInNamespaceOrTypeContext(expression)) + { + return; + } + var generator = SyntaxGenerator.GetGenerator(document); var containingMethod = expression.FirstAncestorOrSelf(node => generator.GetParameterListNode(node) is not null); @@ -106,10 +113,17 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex var singleLineExpression = syntaxFacts.ConvertToSingleLine(expression); var nodeString = singleLineExpression.ToString(); - context.RegisterRefactoring(new CodeActionWithNestedActions( - string.Format(FeaturesResources.Introduce_parameter_for_0, nodeString), actions.Value.actions, isInlinable: false), textSpan); - context.RegisterRefactoring(new CodeActionWithNestedActions( - string.Format(FeaturesResources.Introduce_parameter_for_all_occurrences_of_0, nodeString), actions.Value.actionsAllOccurrences, isInlinable: false), textSpan); + if (actions.Value.actions.Length > 0) + { + context.RegisterRefactoring(new CodeActionWithNestedActions( + string.Format(FeaturesResources.Introduce_parameter_for_0, nodeString), actions.Value.actions, isInlinable: false), textSpan); + } + + if (actions.Value.actionsAllOccurrences.Length > 0) + { + context.RegisterRefactoring(new CodeActionWithNestedActions( + string.Format(FeaturesResources.Introduce_parameter_for_all_occurrences_of_0, nodeString), actions.Value.actionsAllOccurrences, isInlinable: false), textSpan); + } } /// diff --git a/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs index 7011664594501..9c913c0905026 100644 --- a/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -63,9 +61,9 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex private InvertIfStyle GetInvertIfStyle( TIfStatementSyntax ifNode, SemanticModel semanticModel, - out SyntaxNode subsequentSingleExitPointOpt) + out SyntaxNode? subsequentSingleExitPoint) { - subsequentSingleExitPointOpt = null; + subsequentSingleExitPoint = null; if (!IsElseless(ifNode)) { @@ -117,7 +115,7 @@ private InvertIfStyle GetInvertIfStyle( AnalyzeSubsequentControlFlow( semanticModel, subsequentStatementRanges, out var subsequentEndPointIsReachable, - out subsequentSingleExitPointOpt); + out subsequentSingleExitPoint); if (subsequentEndPointIsReachable) { @@ -173,7 +171,7 @@ private InvertIfStyle GetInvertIfStyle( } else if (ifBodyEndPointIsReachable) { - if (subsequentSingleExitPointOpt != null && + if (subsequentSingleExitPoint != null && SingleSubsequentStatement(subsequentStatementRanges)) { // (5) if-body end-point is reachable but the next statement is a only jump-statement. @@ -248,18 +246,18 @@ private async Task InvertIfAsync( TIfStatementSyntax ifNode, CancellationToken cancellationToken) { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var invertIfStyle = GetInvertIfStyle(ifNode, semanticModel, out var subsequentSingleExitPointOpt); - var generator = document.GetLanguageService(); + var invertIfStyle = GetInvertIfStyle(ifNode, semanticModel, out var subsequentSingleExitPoint); + var generator = document.GetRequiredLanguageService(); return document.WithSyntaxRoot( GetRootWithInvertIfStatement( sourceText, root, ifNode, invertIfStyle, - subsequentSingleExitPointOpt, + subsequentSingleExitPoint, negatedExpression: generator.Negate( generator.SyntaxGeneratorInternal, GetCondition(ifNode), @@ -271,10 +269,10 @@ private static void AnalyzeSubsequentControlFlow( SemanticModel semanticModel, ImmutableArray subsequentStatementRanges, out bool subsequentEndPointIsReachable, - out SyntaxNode subsequentSingleExitPointOpt) + out SyntaxNode? subsequentSingleExitPoint) { subsequentEndPointIsReachable = true; - subsequentSingleExitPointOpt = null; + subsequentSingleExitPoint = null; foreach (var statementRange in subsequentStatementRanges) { @@ -282,7 +280,7 @@ private static void AnalyzeSubsequentControlFlow( semanticModel, statementRange, out subsequentEndPointIsReachable, - out subsequentSingleExitPointOpt); + out subsequentSingleExitPoint); if (!subsequentEndPointIsReachable) { @@ -295,14 +293,14 @@ private static void AnalyzeControlFlow( SemanticModel semanticModel, StatementRange statementRange, out bool endPointIsReachable, - out SyntaxNode singleExitPointOpt) + out SyntaxNode? singleExitPoint) { var flow = semanticModel.AnalyzeControlFlow( statementRange.FirstStatement, statementRange.LastStatement); endPointIsReachable = flow.EndPointIsReachable; - singleExitPointOpt = flow.ExitPoints.Length == 1 ? flow.ExitPoints[0] : null; + singleExitPoint = flow.ExitPoints.Length == 1 ? flow.ExitPoints[0] : null; } private static bool SubsequentStatementsAreInTheSameBlock( @@ -381,7 +379,7 @@ private ImmutableArray GetSubsequentStatementRanges(TIfStatement protected abstract string GetTitle(); protected abstract SyntaxList GetStatements(SyntaxNode node); - protected abstract TStatementSyntax GetNextStatement(TStatementSyntax node); + protected abstract TStatementSyntax? GetNextStatement(TStatementSyntax node); protected abstract TStatementSyntax GetJumpStatement(int rawKind); protected abstract int GetJumpStatementRawKind(SyntaxNode node); @@ -413,7 +411,7 @@ protected abstract TIfStatementSyntax UpdateIf( TIfStatementSyntax ifNode, SyntaxNode condition, TEmbeddedStatement trueStatement, - TEmbeddedStatement falseStatementOpt = default); + TEmbeddedStatement? falseStatement = default); protected abstract SyntaxNode WithStatements( SyntaxNode node, @@ -424,7 +422,7 @@ private SyntaxNode GetRootWithInvertIfStatement( SyntaxNode root, TIfStatementSyntax ifNode, InvertIfStyle invertIfStyle, - SyntaxNode subsequentSingleExitPointOpt, + SyntaxNode? subsequentSingleExitPoint, SyntaxNode negatedExpression) { switch (invertIfStyle) @@ -435,8 +433,8 @@ private SyntaxNode GetRootWithInvertIfStatement( text, ifNode: ifNode, condition: negatedExpression, - trueStatement: GetElseBody(ifNode), - falseStatementOpt: GetIfBody(ifNode)); + trueStatement: GetElseBody(ifNode)!, + falseStatement: GetIfBody(ifNode)); return root.ReplaceNode(ifNode, updatedIf); } @@ -448,7 +446,7 @@ private SyntaxNode GetRootWithInvertIfStatement( ifNode: ifNode, condition: negatedExpression, trueStatement: GetEmptyEmbeddedStatement(), - falseStatementOpt: GetIfBody(ifNode)); + falseStatement: GetIfBody(ifNode)); return root.ReplaceNode(ifNode, updatedIf); } @@ -466,7 +464,7 @@ private SyntaxNode GetRootWithInvertIfStatement( case InvertIfStyle.IfWithoutElse_SwapIfBodyWithSubsequentStatements: { - var currentParent = ifNode.Parent; + var currentParent = ifNode.GetRequiredParent(); var statements = GetStatements(currentParent); var index = statements.IndexOf(ifNode); @@ -490,7 +488,7 @@ private SyntaxNode GetRootWithInvertIfStatement( case InvertIfStyle.IfWithoutElse_WithNearmostJumpStatement: { - var currentParent = ifNode.Parent; + var currentParent = ifNode.GetRequiredParent(); var statements = GetStatements(currentParent); var index = statements.IndexOf(ifNode); @@ -515,14 +513,14 @@ private SyntaxNode GetRootWithInvertIfStatement( case InvertIfStyle.IfWithoutElse_WithSubsequentExitPointStatement: { - Debug.Assert(subsequentSingleExitPointOpt is TStatementSyntax); + Debug.Assert(subsequentSingleExitPoint is TStatementSyntax); - var currentParent = ifNode.Parent; + var currentParent = ifNode.GetRequiredParent(); var statements = GetStatements(currentParent); var index = statements.IndexOf(ifNode); var ifBody = GetIfBody(ifNode); - var newIfBody = (TStatementSyntax)subsequentSingleExitPointOpt; + var newIfBody = (TStatementSyntax)subsequentSingleExitPoint; var updatedIf = UpdateIf( text, @@ -542,7 +540,7 @@ private SyntaxNode GetRootWithInvertIfStatement( case InvertIfStyle.IfWithoutElse_MoveSubsequentStatementsToIfBody: { - var currentParent = ifNode.Parent; + var currentParent = ifNode.GetRequiredParent(); var statements = GetStatements(currentParent); var index = statements.IndexOf(ifNode); @@ -565,7 +563,7 @@ private SyntaxNode GetRootWithInvertIfStatement( case InvertIfStyle.IfWithoutElse_WithElseClause: { - var currentParent = ifNode.Parent; + var currentParent = ifNode.GetRequiredParent(); var statements = GetStatements(currentParent); var index = statements.IndexOf(ifNode); @@ -579,7 +577,7 @@ private SyntaxNode GetRootWithInvertIfStatement( ifNode: ifNode, condition: negatedExpression, trueStatement: AsEmbeddedStatement(statementsAfterIf, ifBody), - falseStatementOpt: ifBody); + falseStatement: ifBody); var updatedParent = WithStatements( currentParent, diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index c06f04b943495..fe8226cce6929 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -169,11 +169,11 @@ private void AddDocumentationContent(ISymbol symbol) _documentationMap.Add( SymbolDescriptionGroups.Documentation, - symbol.GetDocumentationParts(_semanticModel, _position, formatter, CancellationToken).ToImmutableArray()); + symbol.GetDocumentationParts(_semanticModel, _position, formatter, CancellationToken)); _documentationMap.Add( SymbolDescriptionGroups.RemarksDocumentation, - symbol.GetRemarksDocumentationParts(_semanticModel, _position, formatter, CancellationToken).ToImmutableArray()); + symbol.GetRemarksDocumentationParts(_semanticModel, _position, formatter, CancellationToken)); AddReturnsDocumentationParts(symbol, formatter); AddValueDocumentationParts(symbol, formatter); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index d3187efe61d82..b05633d3cf97f 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 3f12ca28d393e..24dadfa1fdbb7 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -45,6 +45,8 @@ + + @@ -99,6 +101,7 @@ + diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToUtilities.cs b/src/Features/Core/Portable/NavigateTo/NavigateToUtilities.cs new file mode 100644 index 0000000000000..f44273dc3250b --- /dev/null +++ b/src/Features/Core/Portable/NavigateTo/NavigateToUtilities.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.NavigateTo +{ + internal static class NavigateToUtilities + { + public static ImmutableHashSet GetKindsProvided(Solution solution) + { + var result = ImmutableHashSet.CreateBuilder(StringComparer.Ordinal); + foreach (var project in solution.Projects) + { + var navigateToSearchService = project.GetLanguageService(); + if (navigateToSearchService != null) + result.UnionWith(navigateToSearchService.KindsProvided); + } + + return result.ToImmutable(); + } + + public static TextSpan GetBoundedSpan(INavigableItem item, SourceText sourceText) + { + var spanStart = item.SourceSpan.Start; + var spanEnd = item.SourceSpan.End; + if (item.IsStale) + { + // in the case of a stale item, the span may be out of bounds of the document. Cap + // us to the end of the document as that's where we're going to navigate the user + // to. + spanStart = spanStart > sourceText.Length ? sourceText.Length : spanStart; + spanEnd = spanEnd > sourceText.Length ? sourceText.Length : spanEnd; + } + + return TextSpan.FromBounds(spanStart, spanEnd); + } + } +} diff --git a/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs index 99a212faeb0a3..a87224e638ec3 100644 --- a/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs @@ -21,9 +21,9 @@ internal abstract class GlobalOperationAwareIdleProcessor : IdleProcessor public GlobalOperationAwareIdleProcessor( IAsynchronousOperationListener listener, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, backOffTimeSpanInMs, shutdownToken) + : base(listener, backOffTimeSpan, shutdownToken) { _globalOperation = null; _globalOperationTask = Task.CompletedTask; diff --git a/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs index 5eb3bc29f5447..84618890b7bef 100644 --- a/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs @@ -12,28 +12,28 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler { internal abstract class IdleProcessor { - private const int MinimumDelayInMS = 50; + private static readonly TimeSpan s_minimumDelay = TimeSpan.FromMilliseconds(50); protected readonly IAsynchronousOperationListener Listener; protected readonly CancellationToken CancellationToken; - protected readonly int BackOffTimeSpanInMS; + protected readonly TimeSpan BackOffTimeSpan; // points to processor task private Task? _processorTask; // there is one thread that writes to it and one thread reads from it - private int _lastAccessTimeInMS; + private SharedStopwatch _timeSinceLastAccess; public IdleProcessor( IAsynchronousOperationListener listener, - int backOffTimeSpanInMS, + TimeSpan backOffTimeSpan, CancellationToken cancellationToken) { Listener = listener; CancellationToken = cancellationToken; - BackOffTimeSpanInMS = backOffTimeSpanInMS; - _lastAccessTimeInMS = Environment.TickCount; + BackOffTimeSpan = backOffTimeSpan; + _timeSinceLastAccess = SharedStopwatch.StartNew(); } protected abstract Task WaitAsync(CancellationToken cancellationToken); @@ -46,7 +46,7 @@ protected void Start() } protected void UpdateLastAccessTime() - => _lastAccessTimeInMS = Environment.TickCount; + => _timeSinceLastAccess = SharedStopwatch.StartNew(); protected async Task WaitForIdleAsync(IExpeditableDelaySource expeditableDelaySource) { @@ -57,22 +57,22 @@ protected async Task WaitForIdleAsync(IExpeditableDelaySource expeditableDelaySo return; } - var diffInMS = Environment.TickCount - _lastAccessTimeInMS; - if (diffInMS >= BackOffTimeSpanInMS) + var diff = _timeSinceLastAccess.Elapsed; + if (diff >= BackOffTimeSpan) { return; } // TODO: will safestart/unwarp capture cancellation exception? - var timeLeft = BackOffTimeSpanInMS - diffInMS; - if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(Math.Max(MinimumDelayInMS, timeLeft)), CancellationToken).ConfigureAwait(false)) + var timeLeft = BackOffTimeSpan - diff; + if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(Math.Max(s_minimumDelay.TotalMilliseconds, timeLeft.TotalMilliseconds)), CancellationToken).ConfigureAwait(false)) { - // The delay terminated early to accommodate a blocking operation. Make sure to delay long - // enough that low priority (on idle) operations get a chance to be triggered. + // The delay terminated early to accommodate a blocking operation. Make sure to yield so low + // priority (on idle) operations get a chance to be triggered. // - // 📝 At the time this was discovered, it was not clear exactly why the delay was needed in order - // to avoid live-lock scenarios. - await Task.Delay(TimeSpan.FromMilliseconds(10), CancellationToken).ConfigureAwait(false); + // 📝 At the time this was discovered, it was not clear exactly why the yield (previously delay) + // was needed in order to avoid live-lock scenarios. + await Task.Yield().ConfigureAwait(false); return; } } diff --git a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs index b6eb290a7754d..58529ebc46875 100644 --- a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs +++ b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.SolutionCrawler @@ -16,22 +17,11 @@ internal static class InternalSolutionCrawlerOptions public static readonly Option2 DirectDependencyPropagationOnly = new(nameof(InternalSolutionCrawlerOptions), "Project propagation only on direct dependency", defaultValue: true, storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Project propagation only on direct dependency")); - public static readonly Option2 ActiveFileWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Active file worker backoff timespan in ms", defaultValue: 100, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Active file worker backoff timespan in ms")); - - public static readonly Option2 AllFilesWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "All files worker backoff timespan in ms", defaultValue: 1500, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "All files worker backoff timespan in ms")); - - public static readonly Option2 EntireProjectWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Entire project analysis worker backoff timespan in ms", defaultValue: 5000, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Entire project analysis worker backoff timespan in ms")); - - public static readonly Option2 SemanticChangeBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Semantic change backoff timespan in ms", defaultValue: 100, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Semantic change backoff timespan in ms")); - - public static readonly Option2 ProjectPropagationBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Project propagation backoff timespan in ms", defaultValue: 500, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Project propagation backoff timespan in ms")); - - public static readonly Option2 PreviewBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Preview backoff timespan in ms", defaultValue: 500, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Preview backoff timespan in ms")); + public static readonly TimeSpan ActiveFileWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(100); + public static readonly TimeSpan AllFilesWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(1500); + public static readonly TimeSpan EntireProjectWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(5000); + public static readonly TimeSpan SemanticChangeBackOffTimeSpan = TimeSpan.FromMilliseconds(100); + public static readonly TimeSpan ProjectPropagationBackOffTimeSpan = TimeSpan.FromMilliseconds(500); + public static readonly TimeSpan PreviewBackOffTimeSpan = TimeSpan.FromMilliseconds(500); } } diff --git a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs index 37ae86b2c73bc..2a5e8c14adad1 100644 --- a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs +++ b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs @@ -22,11 +22,6 @@ public InternalSolutionCrawlerOptionsProvider() public ImmutableArray Options { get; } = ImmutableArray.Create( InternalSolutionCrawlerOptions.SolutionCrawler, - InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); + InternalSolutionCrawlerOptions.DirectDependencyPropagationOnly); } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs index 10f16c389c9cd..d84446da3ba92 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs @@ -30,9 +30,9 @@ public AbstractPriorityProcessor( IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) + : base(listener, globalOperationNotificationService, backOffTimeSpan, shutdownToken) { _gate = new object(); _lazyAnalyzers = lazyAnalyzers; diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index b857367086186..33fe20c27b821 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -35,9 +35,9 @@ public HighPriorityProcessor( IAsynchronousOperationListener listener, IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, backOffTimeSpanInMs, shutdownToken) + : base(listener, backOffTimeSpan, shutdownToken) { _processor = processor; _lazyAnalyzers = lazyAnalyzers; diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index 0b5f7d08d5f4a..bb5892d87acfb 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -50,9 +50,9 @@ public IncrementalAnalyzerProcessor( IEnumerable> analyzerProviders, bool initializeLazily, Registration registration, - int highBackOffTimeSpanInMs, - int normalBackOffTimeSpanInMs, - int lowBackOffTimeSpanInMs, + TimeSpan highBackOffTimeSpan, + TimeSpan normalBackOffTimeSpan, + TimeSpan lowBackOffTimeSpan, CancellationToken shutdownToken) { _logAggregator = new LogAggregator(); @@ -81,9 +81,9 @@ public IncrementalAnalyzerProcessor( var globalNotificationService = _registration.Workspace.Services.GetRequiredService(); - _highPriorityProcessor = new HighPriorityProcessor(listener, this, lazyActiveFileAnalyzers, highBackOffTimeSpanInMs, shutdownToken); - _normalPriorityProcessor = new NormalPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, normalBackOffTimeSpanInMs, shutdownToken); - _lowPriorityProcessor = new LowPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, lowBackOffTimeSpanInMs, shutdownToken); + _highPriorityProcessor = new HighPriorityProcessor(listener, this, lazyActiveFileAnalyzers, highBackOffTimeSpan, shutdownToken); + _normalPriorityProcessor = new NormalPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, normalBackOffTimeSpan, shutdownToken); + _lowPriorityProcessor = new LowPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, lowBackOffTimeSpan, shutdownToken); } private static IDiagnosticAnalyzerService? GetDiagnosticAnalyzerService(IEnumerable> analyzerProviders) diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index 19b00ffb7a35b..de3db50989511 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -30,9 +30,9 @@ public LowPriorityProcessor( IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) + : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpan, shutdownToken) { _workItemQueue = new AsyncProjectWorkItemQueue(processor._registration.ProgressReporter, processor._registration.Workspace); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 213ac6b540b6f..681e3ae8c5d49 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -50,9 +50,9 @@ public NormalPriorityProcessor( IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) + : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpan, shutdownToken) { _running = Task.CompletedTask; _workItemQueue = new AsyncDocumentWorkItemQueue(processor._registration.ProgressReporter, processor._registration.Workspace); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs index b5cf17f1660fd..25bdd62a21fe3 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs @@ -41,16 +41,16 @@ public SemanticChangeProcessor( IAsynchronousOperationListener listener, Registration registration, IncrementalAnalyzerProcessor documentWorkerProcessor, - int backOffTimeSpanInMS, - int projectBackOffTimeSpanInMS, + TimeSpan backOffTimeSpan, + TimeSpan projectBackOffTimeSpan, CancellationToken cancellationToken) - : base(listener, backOffTimeSpanInMS, cancellationToken) + : base(listener, backOffTimeSpan, cancellationToken) { _gate = new SemaphoreSlim(initialCount: 0); _registration = registration; - _processor = new ProjectProcessor(listener, registration, documentWorkerProcessor, projectBackOffTimeSpanInMS, cancellationToken); + _processor = new ProjectProcessor(listener, registration, documentWorkerProcessor, projectBackOffTimeSpan, cancellationToken); _workGate = new NonReentrantLock(); _pendingWork = new Dictionary(); @@ -352,9 +352,9 @@ public ProjectProcessor( IAsynchronousOperationListener listener, Registration registration, IncrementalAnalyzerProcessor processor, - int backOffTimeSpanInMS, + TimeSpan backOffTimeSpan, CancellationToken cancellationToken) - : base(listener, backOffTimeSpanInMS, cancellationToken) + : base(listener, backOffTimeSpan, cancellationToken) { _registration = registration; _processor = processor; diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 7cf786abe40fe..430d5e1f23180 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -57,18 +57,18 @@ public WorkCoordinator( _eventProcessingQueue = new TaskQueue(listener, TaskScheduler.Default); - var activeFileBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS); - var allFilesWorkerBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS); - var entireProjectWorkerBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS); + var activeFileBackOffTimeSpan = InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpan; + var allFilesWorkerBackOffTimeSpan = InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpan; + var entireProjectWorkerBackOffTimeSpan = InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpan; _documentAndProjectWorkerProcessor = new IncrementalAnalyzerProcessor( listener, analyzerProviders, initializeLazily, _registration, - activeFileBackOffTimeSpanInMS, allFilesWorkerBackOffTimeSpanInMS, entireProjectWorkerBackOffTimeSpanInMS, _shutdownToken); + activeFileBackOffTimeSpan, allFilesWorkerBackOffTimeSpan, entireProjectWorkerBackOffTimeSpan, _shutdownToken); - var semanticBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS); - var projectBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS); + var semanticBackOffTimeSpan = InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpan; + var projectBackOffTimeSpan = InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpan; - _semanticChangeProcessor = new SemanticChangeProcessor(listener, _registration, _documentAndProjectWorkerProcessor, semanticBackOffTimeSpanInMS, projectBackOffTimeSpanInMS, _shutdownToken); + _semanticChangeProcessor = new SemanticChangeProcessor(listener, _registration, _documentAndProjectWorkerProcessor, semanticBackOffTimeSpan, projectBackOffTimeSpan, _shutdownToken); // if option is on if (_optionService.GetOption(InternalSolutionCrawlerOptions.SolutionCrawler)) diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index e5259c283166b..12d1569b8909d 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -34,6 +35,7 @@ public static async Task> GetUnusedReferencesAsync .Where(project => projectFilePath.Equals(project.FilePath, System.StringComparison.OrdinalIgnoreCase)); HashSet usedAssemblyFilePaths = new(); + HashSet usedProjectFileNames = new(); foreach (var project in projects) { @@ -50,13 +52,21 @@ public static async Task> GetUnusedReferencesAsync .OfType() .Select(reference => reference.FilePath) .WhereNotNull()); + + // Compilation references do not contain the full path to the output assembly so we track them + // by file name. + usedProjectFileNames.AddRange(usedAssemblyReferences + .OfType() + .Select(reference => reference.Compilation.SourceModule.MetadataName) + .WhereNotNull()); } - return GetUnusedReferences(usedAssemblyFilePaths, references); + return GetUnusedReferences(usedAssemblyFilePaths, usedProjectFileNames, references); } internal static ImmutableArray GetUnusedReferences( HashSet usedAssemblyFilePaths, + HashSet usedProjectFileNames, ImmutableArray references) { var unusedReferencesBuilder = ImmutableArray.CreateBuilder(); @@ -85,7 +95,8 @@ internal static ImmutableArray GetUnusedReferences( var unusedReferences = RemoveDirectlyUsedReferences( referencesForReferenceType, - usedAssemblyFilePaths); + usedAssemblyFilePaths, + usedProjectFileNames); // Update with the references that are remaining. if (unusedReferences.IsEmpty) @@ -119,7 +130,8 @@ internal static ImmutableArray GetUnusedReferences( private static ImmutableArray RemoveDirectlyUsedReferences( ImmutableArray references, - HashSet usedAssemblyFilePaths) + HashSet usedAssemblyFilePaths, + HashSet usedProjectFileNames) { // In this method we will check if a reference directly brings in a used compilation assembly. // @@ -131,19 +143,42 @@ private static ImmutableArray RemoveDirectlyUsedReferences( foreach (var reference in references) { - // We will look at the compilation assemblies brought in directly by the - // references to see if they are used. - if (!reference.CompilationAssemblies.Any(usedAssemblyFilePaths.Contains)) + if (reference.ReferenceType == ReferenceType.Project) { - // None of the assemblies brought into this compilation are in the - // used assemblies list, so we will consider the reference unused. - unusedReferencesBuilder.Add(reference); - continue; + // Since we only know project references by their CompilationReference which + // does not include the full output path. We look only at the file name of the + // compilation assembly and compare it with our list of used project assembly names. + var projectAssemblyFileNames = reference.CompilationAssemblies + .SelectAsArray(assemblyPath => Path.GetFileName(assemblyPath)); + + // We will look at the project assemblies brought in directly by the + // references to see if they are used. + if (!projectAssemblyFileNames.Any(usedProjectFileNames.Contains)) + { + // None of the project assemblies brought into this compilation are in the + // used assemblies list, so we will consider the reference unused. + unusedReferencesBuilder.Add(reference); + continue; + } + + // Remove the project file name now that we've identified it. + usedProjectFileNames.ExceptWith(projectAssemblyFileNames); + } + else + { + // We will look at the compilation assemblies brought in directly by the + // references to see if they are used. + if (!reference.CompilationAssemblies.Any(usedAssemblyFilePaths.Contains)) + { + // None of the assemblies brought into this compilation are in the + // used assemblies list, so we will consider the reference unused. + unusedReferencesBuilder.Add(reference); + continue; + } } // Remove all assemblies that are brought into this compilation by this reference. usedAssemblyFilePaths.ExceptWith(GetAllCompilationAssemblies(reference)); - } return unusedReferencesBuilder.ToImmutable(); diff --git a/src/Features/Core/Portable/ValueTracking/IValueTrackingService.cs b/src/Features/Core/Portable/ValueTracking/IValueTrackingService.cs new file mode 100644 index 0000000000000..7f21b4c70ef30 --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/IValueTrackingService.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + internal interface IValueTrackingService : IWorkspaceService + { + Task> TrackValueSourceAsync(TextSpan selection, Document document, CancellationToken cancellationToken); + Task> TrackValueSourceAsync(Solution solution, ValueTrackedItem previousTrackedItem, CancellationToken cancellationToken); + } + + internal interface IRemoteValueTrackingService + { + ValueTask> TrackValueSourceAsync(PinnedSolutionInfo solutionInfo, TextSpan selection, DocumentId document, CancellationToken cancellationToken); + ValueTask> TrackValueSourceAsync(PinnedSolutionInfo solutionInfo, SerializableValueTrackedItem previousTrackedItem, CancellationToken cancellationToken); + } +} diff --git a/src/Features/Core/Portable/ValueTracking/SerializableValueTrackedItem.cs b/src/Features/Core/Portable/ValueTracking/SerializableValueTrackedItem.cs new file mode 100644 index 0000000000000..e5840f18ea09e --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/SerializableValueTrackedItem.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + [DataContract] + internal sealed class SerializableValueTrackedItem + { + [DataMember(Order = 0)] + public SymbolKey SymbolKey { get; } + + [DataMember(Order = 1)] + public TextSpan TextSpan { get; } + + [DataMember(Order = 2)] + public DocumentId DocumentId { get; } + + [DataMember(Order = 3)] + public SerializableValueTrackedItem? Parent { get; } + + public SerializableValueTrackedItem( + SymbolKey symbolKey, + TextSpan textSpan, + DocumentId documentId, + SerializableValueTrackedItem? parent = null) + { + SymbolKey = symbolKey; + Parent = parent; + TextSpan = textSpan; + DocumentId = documentId; + } + + public static SerializableValueTrackedItem Dehydrate(Solution solution, ValueTrackedItem valueTrackedItem, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var parent = valueTrackedItem.Parent is null + ? null + : Dehydrate(solution, valueTrackedItem.Parent, cancellationToken); + + return new SerializableValueTrackedItem(valueTrackedItem.SymbolKey, valueTrackedItem.Span, valueTrackedItem.DocumentId, parent); + } + + public async Task RehydrateAsync(Solution solution, CancellationToken cancellationToken) + { + var document = solution.GetRequiredDocument(DocumentId); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var symbolResolution = SymbolKey.Resolve(semanticModel.Compilation, cancellationToken: cancellationToken); + + if (symbolResolution.Symbol is null) + { + return null; + } + + cancellationToken.ThrowIfCancellationRequested(); + var parent = Parent is null ? null : await Parent.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + + return await ValueTrackedItem.TryCreateAsync(document, TextSpan, symbolResolution.Symbol, parent, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Features/Core/Portable/ValueTracking/ValueTrackedItem.cs b/src/Features/Core/Portable/ValueTracking/ValueTrackedItem.cs new file mode 100644 index 0000000000000..5d4654fd52358 --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/ValueTrackedItem.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + internal class ValueTrackedItem + { + public SymbolKey SymbolKey { get; } + public ValueTrackedItem? Parent { get; } + + public DocumentId DocumentId { get; } + public TextSpan Span { get; } + public ImmutableArray ClassifiedSpans { get; } + public SourceText SourceText { get; } + public Glyph Glyph { get; } + + private ValueTrackedItem( + SymbolKey symbolKey, + SourceText sourceText, + ImmutableArray classifiedSpans, + TextSpan textSpan, + DocumentId documentId, + Glyph glyph, + ValueTrackedItem? parent = null) + { + SymbolKey = symbolKey; + Parent = parent; + Glyph = glyph; + Span = textSpan; + ClassifiedSpans = classifiedSpans; + SourceText = sourceText; + DocumentId = documentId; + } + + public override string ToString() + { + var subText = SourceText.GetSubText(Span); + return subText.ToString(); + } + + public static Task TryCreateAsync(Solution solution, Location location, ISymbol symbol, ValueTrackedItem? parent = null, CancellationToken cancellationToken = default) + { + Contract.ThrowIfNull(location.SourceTree); + + var document = solution.GetRequiredDocument(location.SourceTree); + return TryCreateAsync(document, location.SourceSpan, symbol, parent, cancellationToken); + } + + public static async Task TryCreateAsync(Document document, TextSpan textSpan, ISymbol symbol, ValueTrackedItem? parent = null, CancellationToken cancellationToken = default) + { + var excerptService = document.Services.GetService(); + SourceText? sourceText = null; + ImmutableArray classifiedSpans = default; + + if (excerptService != null) + { + var result = await excerptService.TryExcerptAsync(document, textSpan, ExcerptMode.SingleLine, cancellationToken).ConfigureAwait(false); + if (result.HasValue) + { + var value = result.Value; + sourceText = value.Content; + } + } + + if (sourceText is null) + { + var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, cancellationToken).ConfigureAwait(false); + classifiedSpans = classificationResult.ClassifiedSpans; + var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + sourceText = await syntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + } + + return new ValueTrackedItem( + SymbolKey.Create(symbol, cancellationToken), + sourceText, + classifiedSpans, + textSpan, + document.Id, + symbol.GetGlyph(), + parent: parent); + } + } +} diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs new file mode 100644 index 0000000000000..017373779b62a --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + internal static partial class ValueTracker + { + private class FindReferencesProgress : IStreamingFindReferencesProgress, IStreamingProgressTracker + { + private readonly OperationCollector _operationCollector; + public FindReferencesProgress(OperationCollector valueTrackingProgressCollector) + { + _operationCollector = valueTrackingProgressCollector; + } + + public IStreamingProgressTracker ProgressTracker => this; + + public ValueTask AddItemsAsync(int count, CancellationToken _) => new(); + + public ValueTask ItemCompletedAsync(CancellationToken _) => new(); + + public ValueTask OnCompletedAsync(CancellationToken _) => new(); + + public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new(); + + public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken _) => new(); + + public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken _) => new(); + + public async ValueTask OnReferenceFoundAsync(SymbolGroup _, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + { + if (!location.Location.IsInSource) + { + return; + } + + if (symbol is IMethodSymbol methodSymbol) + { + if (methodSymbol.IsConstructor()) + { + await TrackConstructorAsync(location, cancellationToken).ConfigureAwait(false); + } + else + { + // If we're searching for references to a method, we don't want to store the symbol as that method again. Instead + // we want to track the invocations and how to follow their source + await TrackMethodInvocationArgumentsAsync(location, cancellationToken).ConfigureAwait(false); + } + } + else if (location.IsWrittenTo) + { + var syntaxFacts = location.Document.GetRequiredLanguageService(); + var node = location.Location.FindNode(CancellationToken.None); + + // Assignments to a member using a "this." or "Me." result in the node being an + // identifier and the parent of the node being the member access expression. The member + // access expression gives the right value for "IsLeftSideOfAnyAssignment" but also + // gives the correct operation, where as the IdentifierSyntax does not. + if (node.Parent is not null && syntaxFacts.IsAnyMemberAccessExpression(node.Parent)) + { + node = node.Parent; + } + + if (syntaxFacts.IsLeftSideOfAnyAssignment(node)) + { + await AddItemsFromAssignmentAsync(location.Document, node, _operationCollector, cancellationToken).ConfigureAwait(false); + } + else + { + var semanticModel = await location.Document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var operation = semanticModel.GetOperation(node, cancellationToken); + if (operation is null) + { + return; + } + + await _operationCollector.VisitAsync(operation, cancellationToken).ConfigureAwait(false); + } + } + } + + public ValueTask OnStartedAsync(CancellationToken _) => new(); + + private async Task TrackConstructorAsync(ReferenceLocation referenceLocation, CancellationToken cancellationToken) + { + var document = referenceLocation.Document; + var span = referenceLocation.Location.SourceSpan; + + var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var originalNode = syntaxRoot.FindNode(span); + + if (originalNode is null || originalNode.Parent is null) + { + return; + } + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var operation = semanticModel.GetOperation(originalNode.Parent, cancellationToken); + if (operation is not IObjectCreationOperation) + { + return; + } + + await _operationCollector.VisitAsync(operation, cancellationToken).ConfigureAwait(false); + } + + private async Task TrackMethodInvocationArgumentsAsync(ReferenceLocation referenceLocation, CancellationToken cancellationToken) + { + var document = referenceLocation.Document; + var span = referenceLocation.Location.SourceSpan; + + var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var originalNode = syntaxRoot.FindNode(span); + + if (originalNode is null) + { + return; + } + + var syntaxFacts = document.GetRequiredLanguageService(); + var invocationSyntax = originalNode.FirstAncestorOrSelf(syntaxFacts.IsInvocationExpression); + if (invocationSyntax is null) + { + return; + } + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var operation = semanticModel.GetOperation(invocationSyntax, cancellationToken); + if (operation is not IInvocationOperation) + { + return; + } + + await _operationCollector.VisitAsync(operation, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs new file mode 100644 index 0000000000000..87c09da0ac71d --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + internal static partial class ValueTracker + { + private class OperationCollector + { + public ValueTrackingProgressCollector ProgressCollector { get; } + public Solution Solution { get; } + + public OperationCollector(ValueTrackingProgressCollector progressCollector, Solution solution) + { + ProgressCollector = progressCollector; + Solution = solution; + } + + public Task VisitAsync(IOperation operation, CancellationToken cancellationToken) + => operation switch + { + IObjectCreationOperation objectCreationOperation => VisitObjectCreationAsync(objectCreationOperation, cancellationToken), + IInvocationOperation invocationOperation => VisitInvocationAsync(invocationOperation, cancellationToken), + ILiteralOperation literalOperation => VisitLiteralAsync(literalOperation, cancellationToken), + IReturnOperation returnOperation => VisitReturnAsync(returnOperation, cancellationToken), + IArgumentOperation argumentOperation => ShouldTrackArgument(argumentOperation) ? VisitAsync(argumentOperation.Value, cancellationToken) : Task.CompletedTask, + ILocalReferenceOperation or + IParameterReferenceOperation or + IFieldReferenceOperation or + IPropertyReferenceOperation => VisitReferenceAsync(operation, cancellationToken), + + IAssignmentOperation assignmentOperation => VisitAssignmentOperationAsync(assignmentOperation, cancellationToken), + IMethodBodyOperation methodBodyOperation => VisitReturnDescendentsAsync(methodBodyOperation, allowImplicit: true, cancellationToken), + IBlockOperation blockOperation => VisitReturnDescendentsAsync(blockOperation, allowImplicit: false, cancellationToken), + + // Default to reporting if there is symbol information available + _ => VisitDefaultAsync(operation, cancellationToken) + }; + + private async Task VisitReturnDescendentsAsync(IOperation operation, bool allowImplicit, CancellationToken cancellationToken) + { + var returnOperations = operation.Descendants().Where(d => d is IReturnOperation && (allowImplicit || !d.IsImplicit)); + foreach (var returnOperation in returnOperations) + { + cancellationToken.ThrowIfCancellationRequested(); + await VisitAsync(returnOperation, cancellationToken).ConfigureAwait(false); + } + } + + private async Task VisitDefaultAsync(IOperation operation, CancellationToken cancellationToken) + { + // If an operation has children, desend in them by default. + // For cases that should not be descendend into, they should be explicitly handled + // in VisitAsync. + // Ex: Binary operation of [| x + y |] + // both x and y should be evaluated separately + var childrenVisited = await TryVisitChildrenAsync(operation, cancellationToken).ConfigureAwait(false); + + // In cases where the child operations were visited, they would be added instead of the parent + // currently being evaluated. Do not add the parent as well since it would be redundent. + // Ex: Binary operation of [| x + y |] + // both x and y should be evaluated separately, but the whole operation should not be reported + if (childrenVisited) + { + return; + } + + var semanticModel = operation.SemanticModel; + if (semanticModel is null) + { + return; + } + + var symbolInfo = semanticModel.GetSymbolInfo(operation.Syntax, cancellationToken); + if (symbolInfo.Symbol is null) + { + return; + } + + await AddOperationAsync(operation, symbolInfo.Symbol, cancellationToken).ConfigureAwait(false); + } + + private async Task TryVisitChildrenAsync(IOperation operation, CancellationToken cancellationToken) + { + foreach (var child in operation.Children) + { + await VisitAsync(child, cancellationToken).ConfigureAwait(false); + } + + return operation.Children.Any(); + } + + private Task VisitAssignmentOperationAsync(IAssignmentOperation assignmentOperation, CancellationToken cancellationToken) + => VisitAsync(assignmentOperation.Value, cancellationToken); + + private Task VisitObjectCreationAsync(IObjectCreationOperation objectCreationOperation, CancellationToken cancellationToken) + => TrackArgumentsAsync(objectCreationOperation.Arguments, cancellationToken); + + private async Task VisitInvocationAsync(IInvocationOperation invocationOperation, CancellationToken cancellationToken) + { + await AddOperationAsync(invocationOperation, invocationOperation.TargetMethod, cancellationToken).ConfigureAwait(false); + await TrackArgumentsAsync(invocationOperation.Arguments, cancellationToken).ConfigureAwait(false); + } + + private Task VisitReferenceAsync(IOperation operation, CancellationToken cancellationToken) + { + Debug.Assert(operation is + ILocalReferenceOperation or + IParameterReferenceOperation or + IFieldReferenceOperation or + IPropertyReferenceOperation); + + if (IsContainedIn(operation, out var argumentOperation) && argumentOperation.Parameter is not null) + { + if (argumentOperation.Parameter.IsRefOrOut()) + { + // Always add ref or out parameters to track as assignments since the values count as + // assignments across method calls for the purposes of value tracking. + return AddOperationAsync(operation, argumentOperation.Parameter, cancellationToken); + } + + // If the parameter is not a ref or out param, track the reference assignments that count + // as input to the argument being passed to the method. + return AddReference(operation, cancellationToken); + } + + if (IsContainedIn(operation) || IsContainedIn(operation)) + { + // If the reference is part of a return operation or assignment operation we want to track where the values come from + // since they contribute to the "output" of the method/assignment and are relavent for value tracking. + return AddReference(operation, cancellationToken); + } + + return Task.CompletedTask; + + Task AddReference(IOperation operation, CancellationToken cancellationToken) + => operation switch + { + IParameterReferenceOperation parameterReference => AddOperationAsync(operation, parameterReference.Parameter, cancellationToken), + IFieldReferenceOperation fieldReferenceOperation => AddOperationAsync(operation, fieldReferenceOperation.Member, cancellationToken), + IPropertyReferenceOperation propertyReferenceOperation => AddOperationAsync(operation, propertyReferenceOperation.Member, cancellationToken), + ILocalReferenceOperation localReferenceOperation => AddOperationAsync(operation, localReferenceOperation.Local, cancellationToken), + _ => Task.CompletedTask + }; + } + + private Task VisitLiteralAsync(ILiteralOperation literalOperation, CancellationToken cancellationToken) + { + if (literalOperation.Type is null) + { + return Task.CompletedTask; + } + + return AddOperationAsync(literalOperation, literalOperation.Type, cancellationToken); + } + + private Task VisitReturnAsync(IReturnOperation returnOperation, CancellationToken cancellationToken) + { + if (returnOperation.ReturnedValue is null) + { + return Task.CompletedTask; + } + + return VisitAsync(returnOperation.ReturnedValue, cancellationToken); + } + + private async Task AddOperationAsync(IOperation operation, ISymbol symbol, CancellationToken cancellationToken) + { + _ = await ProgressCollector.TryReportAsync( + Solution, + operation.Syntax.GetLocation(), + symbol, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private async Task TrackArgumentsAsync(ImmutableArray argumentOperations, CancellationToken cancellationToken) + { + var collectorsAndArgumentMap = argumentOperations + .Where(ShouldTrackArgument) + // Clone the collector here to allow each argument to report multiple items. + // See Clone() docs for more details + .Select(argument => (collector: Clone(), argument)) + .ToImmutableArray(); + + var tasks = collectorsAndArgumentMap + .Select(pair => Task.Run(() => pair.collector.VisitAsync(pair.argument, cancellationToken))); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + var items = collectorsAndArgumentMap + .Select(pair => pair.collector.ProgressCollector) + .SelectMany(collector => collector.GetItems()) + .Reverse(); // ProgressCollector uses a Stack, and we want to maintain the order by arguments, so reverse + + foreach (var item in items) + { + ProgressCollector.Report(item); + } + } + + /// + /// Clone the current collector into a new one with + /// the same parent but a separate progress collector. + /// This allows collection of items given the same state + /// as this collector while also keeping them "grouped" separately. + /// + /// + /// This is useful for cases such as tracking arguments, where each + /// argument may be an expression or something else. We want to track each + /// argument expression in the correct order, but a single argument may produce + /// multiple items. By cloning we can track the items for each argument and then + /// gather them all at the end to report in the correct order. + /// + private OperationCollector Clone() + { + var collector = new ValueTrackingProgressCollector + { + Parent = ProgressCollector.Parent + }; + return new OperationCollector(collector, Solution); + } + + private static bool ShouldTrackArgument(IArgumentOperation argumentOperation) + { + // Ref or Out arguments always contribute data as "assignments" + // across method calls + if (argumentOperation.Parameter?.IsRefOrOut() == true) + { + return true; + } + + // If the argument value is an expression, binary operation, or + // invocation then parts of the operation need to be evaluated + // to see if they contribute data for value tracking + if (argumentOperation.Value is IExpressionStatementOperation + or IBinaryOperation + or IInvocationOperation) + { + return true; + } + + // If the argument value is a parameter reference, then the method calls + // leading to that parameter value should be tracked as well. + // Ex: + // string Prepend(string s1) => "pre" + s1; + // string CallPrepend(string [|s2|]) => Prepend(s2); + // Tracking [|s2|] into calls as an argument means that we + // need to know where [|s2|] comes from and how it contributes + // to the value s1 + if (argumentOperation.Value is IParameterReferenceOperation) + { + return true; + } + + // A literal value as an argument is a dead end for data, but still contributes + // to a value and should be shown in value tracking. It should never expand + // further though. + // Ex: + // string Prepend(string [|s|]) => "pre" + s; + // string DefaultPrepend() => Prepend("default"); + // [|s|] is the parameter we need to track values for, which + // is assigned to "default" in DefaultPrepend + if (argumentOperation.Value is ILiteralOperation) + { + return true; + } + + return false; + } + + private static bool IsContainedIn(IOperation? operation) where TContainingOperation : IOperation + => IsContainedIn(operation, out var _); + + private static bool IsContainedIn(IOperation? operation, [NotNullWhen(returnValue: true)] out TContainingOperation? containingOperation) where TContainingOperation : IOperation + { + while (operation is not null) + { + if (operation is TContainingOperation tmpOperation) + { + containingOperation = tmpOperation; + return true; + } + + operation = operation.Parent; + } + + containingOperation = default; + return false; + } + } + } +} diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.cs new file mode 100644 index 0000000000000..7b60d5dffc3af --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.cs @@ -0,0 +1,334 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + internal static partial class ValueTracker + { + public static async Task TrackValueSourceAsync( + TextSpan selection, + Document document, + ValueTrackingProgressCollector progressCollector, + CancellationToken cancellationToken) + { + var (symbol, node) = await GetSelectedSymbolAsync(selection, document, cancellationToken).ConfigureAwait(false); + var operationCollector = new OperationCollector(progressCollector, document.Project.Solution); + + if (symbol + is IPropertySymbol + or IFieldSymbol + or ILocalSymbol + or IParameterSymbol) + { + RoslynDebug.AssertNotNull(node); + + var solution = document.Project.Solution; + var declaringSyntaxReferences = symbol.DeclaringSyntaxReferences; + var syntaxFacts = document.GetRequiredLanguageService(); + + // If the selection is within a declaration of the symbol, we want to include + // all declarations and assignments of the symbol + if (declaringSyntaxReferences.Any(r => r.Span.IntersectsWith(selection))) + { + // Add all initializations of the symbol. Those are not caught in + // the reference finder but should still show up in the tree + foreach (var syntaxRef in declaringSyntaxReferences) + { + var location = Location.Create(syntaxRef.SyntaxTree, syntaxRef.Span); + await progressCollector.TryReportAsync(solution, location, symbol, cancellationToken).ConfigureAwait(false); + } + + await TrackVariableReferencesAsync(symbol, operationCollector, cancellationToken).ConfigureAwait(false); + } + // The selection is not on a declaration, check that the node + // is on the left side of an assignment. If so, populate so we can + // track the RHS values that contribute to this value + else if (syntaxFacts.IsLeftSideOfAnyAssignment(node)) + { + await AddItemsFromAssignmentAsync(document, node, operationCollector, cancellationToken).ConfigureAwait(false); + } + // Not on the left part of an assignment? Then just add an item with the statement + // and the symbol. It should be the top item, and children will find the sources + // of the value. A good example is a return statement, such as "return $$x", + // where $$ is the cursor position. The top item should have the return statement for + // context, and the remaining items should expand into the assignments of x + else + { + await progressCollector.TryReportAsync(document.Project.Solution, node.GetLocation(), symbol, cancellationToken).ConfigureAwait(false); + } + } + } + + public static async Task TrackValueSourceAsync( + Solution solution, + ValueTrackedItem previousTrackedItem, + ValueTrackingProgressCollector progressCollector, + CancellationToken cancellationToken) + { + progressCollector.Parent = previousTrackedItem; + var operationCollector = new OperationCollector(progressCollector, solution); + var symbol = await GetSymbolAsync(previousTrackedItem, solution, cancellationToken).ConfigureAwait(false); + + switch (symbol) + { + case ILocalSymbol: + case IPropertySymbol: + case IFieldSymbol: + { + // The "output" is a variable assignment, track places where it gets assigned and defined + await TrackVariableDefinitionsAsync(symbol, operationCollector, cancellationToken).ConfigureAwait(false); + await TrackVariableReferencesAsync(symbol, operationCollector, cancellationToken).ConfigureAwait(false); + } + + break; + + case IParameterSymbol parameterSymbol: + { + var previousSymbol = await GetSymbolAsync(previousTrackedItem.Parent, solution, cancellationToken).ConfigureAwait(false); + + // If the current parameter is a parameter symbol for the previous tracked method it should be treated differently. + // For example: + // string PrependString(string pre, string s) => pre + s; + // ^--- previously tracked ^---- current parameter being tracked + // + // In this case, s is being tracked because it contributed to the return of the method. We only + // want to track assignments to s that could impact the return rather than tracking the same method + // twice. + var isParameterForPreviousTrackedMethod = previousSymbol?.Equals(parameterSymbol.ContainingSymbol, SymbolEqualityComparer.Default) == true; + + // For Ref or Out parameters, they contribute data across method calls through assignments + // within the method. No need to track returns + // Ex: TryGetValue("mykey", out var [|v|]) + // [|v|] is the interesting part, we don't care what the method returns + var isRefOrOut = parameterSymbol.IsRefOrOut(); + + // Always track the parameter assignments as variables, in case they are assigned anywhere in the method + await TrackVariableReferencesAsync(parameterSymbol, operationCollector, cancellationToken).ConfigureAwait(false); + + var trackMethod = !(isParameterForPreviousTrackedMethod || isRefOrOut); + if (trackMethod) + { + await TrackParameterSymbolAsync(parameterSymbol, operationCollector, cancellationToken).ConfigureAwait(false); + } + } + + break; + + case IMethodSymbol methodSymbol: + { + // The "output" is from a method, meaning it has a return or out param that is used. Track those + await TrackMethodSymbolAsync(methodSymbol, operationCollector, cancellationToken).ConfigureAwait(false); + } + + break; + } + } + + private static async Task AddItemsFromAssignmentAsync(Document document, SyntaxNode lhsNode, OperationCollector collector, CancellationToken cancellationToken) + { + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var operation = semanticModel.GetOperation(lhsNode, cancellationToken); + if (operation is null) + { + return; + } + + IAssignmentOperation? assignmentOperation = null; + + while (assignmentOperation is null + && operation is not null) + { + assignmentOperation = operation as IAssignmentOperation; + operation = operation.Parent; + } + + if (assignmentOperation is null) + { + return; + } + + await collector.VisitAsync(assignmentOperation, cancellationToken).ConfigureAwait(false); + } + + private static async Task TrackVariableReferencesAsync(ISymbol symbol, OperationCollector collector, CancellationToken cancellationToken) + { + var findReferenceProgressCollector = new FindReferencesProgress(collector); + await SymbolFinder.FindReferencesAsync( + symbol, + collector.Solution, + findReferenceProgressCollector, + documents: null, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); + } + + private static async Task TrackParameterSymbolAsync( + IParameterSymbol parameterSymbol, + OperationCollector collector, + CancellationToken cancellationToken) + { + var containingMethod = (IMethodSymbol)parameterSymbol.ContainingSymbol; + var findReferenceProgressCollector = new FindReferencesProgress(collector); + await SymbolFinder.FindReferencesAsync( + containingMethod, + collector.Solution, + findReferenceProgressCollector, + documents: null, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); + } + + private static async Task TrackMethodSymbolAsync(IMethodSymbol methodSymbol, OperationCollector collector, CancellationToken cancellationToken) + { + var hasAnyOutData = HasAValueReturn(methodSymbol) || HasAnOutOrRefParam(methodSymbol); + if (!hasAnyOutData) + { + // With no out data, there's nothing to do here + return; + } + + // TODO: Use DFA to find meaningful returns? https://github.com/dotnet/roslyn-analyzers/blob/9e5f533cbafcc5579e4d758bc9bde27b7611ca54/docs/Writing%20dataflow%20analysis%20based%20analyzers.md + if (HasAValueReturn(methodSymbol)) + { + foreach (var location in methodSymbol.GetDefinitionLocationsToShow()) + { + if (location.SourceTree is null) + { + continue; + } + + var node = location.FindNode(cancellationToken); + var sourceDoc = collector.Solution.GetRequiredDocument(location.SourceTree); + var syntaxFacts = sourceDoc.GetRequiredLanguageService(); + var semanticModel = await sourceDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var operation = semanticModel.GetOperation(node, cancellationToken); + + // In VB the parent node contains the operation (IBlockOperation) instead of the one returned + // by the symbol location. + if (operation is null && node.Parent is not null) + { + operation = semanticModel.GetOperation(node.Parent, cancellationToken); + } + + if (operation is null) + { + continue; + } + + await collector.VisitAsync(operation, cancellationToken).ConfigureAwait(false); + } + } + + if (HasAnOutOrRefParam(methodSymbol)) + { + foreach (var outOrRefParam in methodSymbol.Parameters.Where(p => p.IsRefOrOut())) + { + if (!outOrRefParam.IsFromSource()) + { + continue; + } + + await TrackVariableReferencesAsync(outOrRefParam, collector, cancellationToken).ConfigureAwait(false); + } + } + + // TODO check for Task + static bool HasAValueReturn(IMethodSymbol methodSymbol) + { + return methodSymbol.ReturnType.SpecialType != SpecialType.System_Void; + } + + static bool HasAnOutOrRefParam(IMethodSymbol methodSymbol) + { + return methodSymbol.Parameters.Any(p => p.IsRefOrOut()); + } + } + + private static async Task<(ISymbol?, SyntaxNode?)> GetSelectedSymbolAsync(TextSpan textSpan, Document document, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var selectedNode = root.FindNode(textSpan); + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var selectedSymbol = + semanticModel.GetSymbolInfo(selectedNode, cancellationToken).Symbol + ?? semanticModel.GetDeclaredSymbol(selectedNode, cancellationToken); + + if (selectedSymbol is null) + { + var syntaxFacts = document.GetRequiredLanguageService(); + + // If the node is an argument it's possible that it's just + // an identifier in the expression. If so, then grab the symbol + // for that node instead of the argument. + // EX: MyMethodCall($$x, y) should get the identifier x and + // the symbol for that identifier + if (syntaxFacts.IsArgument(selectedNode)) + { + selectedNode = syntaxFacts.GetExpressionOfArgument(selectedNode)!; + selectedSymbol = semanticModel.GetSymbolInfo(selectedNode, cancellationToken).Symbol; + } + } + + return (selectedSymbol, selectedNode); + } + + private static async Task TrackVariableDefinitionsAsync(ISymbol symbol, OperationCollector collector, CancellationToken cancellationToken) + { + foreach (var definitionLocation in symbol.Locations) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (definitionLocation is not { SourceTree: not null }) + { + continue; + } + + var node = definitionLocation.FindNode(cancellationToken); + var document = collector.Solution.GetRequiredDocument(node.SyntaxTree); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var operation = semanticModel.GetOperation(node, cancellationToken); + + var declarators = operation switch + { + IVariableDeclaratorOperation variableDeclarator => ImmutableArray.Create(variableDeclarator), + IVariableDeclarationOperation variableDeclaration => variableDeclaration.Declarators, + _ => ImmutableArray.Empty + }; + + foreach (var declarator in declarators) + { + var initializer = declarator.GetVariableInitializer(); + if (initializer is null) + { + continue; + } + + await collector.VisitAsync(initializer, cancellationToken).ConfigureAwait(false); + } + } + } + + private static async Task GetSymbolAsync(ValueTrackedItem? item, Solution solution, CancellationToken cancellationToken) + { + if (item is null) + { + return null; + } + + var document = solution.GetRequiredDocument(item.DocumentId); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return item.SymbolKey.Resolve(semanticModel.Compilation, cancellationToken: cancellationToken).Symbol; + } + } +} diff --git a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs new file mode 100644 index 0000000000000..5794dba89218f --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + internal class ValueTrackingProgressCollector : IProgress + { + private readonly object _lock = new(); + private readonly Stack _items = new(); + + public event EventHandler? OnNewItem; + + internal ValueTrackedItem? Parent { get; set; } + + public void Report(ValueTrackedItem item) + { + lock (_lock) + { + _items.Push(item); + } + + OnNewItem?.Invoke(null, item); + } + + public ImmutableArray GetItems() + { + lock (_lock) + { + return _items.ToImmutableArray(); + } + } + + internal async Task TryReportAsync(Solution solution, Location location, ISymbol symbol, CancellationToken cancellationToken = default) + { + var item = await ValueTrackedItem.TryCreateAsync(solution, location, symbol, Parent, cancellationToken).ConfigureAwait(false); + if (item is not null) + { + Report(item); + return true; + } + + return false; + } + } +} diff --git a/src/Features/Core/Portable/ValueTracking/ValueTrackingService.cs b/src/Features/Core/Portable/ValueTracking/ValueTrackingService.cs new file mode 100644 index 0000000000000..5b7414919cce7 --- /dev/null +++ b/src/Features/Core/Portable/ValueTracking/ValueTrackingService.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ValueTracking +{ + [ExportWorkspaceService(typeof(IValueTrackingService)), Shared] + internal partial class ValueTrackingService : IValueTrackingService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ValueTrackingService() + { + } + + public async Task> TrackValueSourceAsync( + TextSpan selection, + Document document, + CancellationToken cancellationToken) + { + using var logger = Logger.LogBlock(FunctionId.ValueTracking_TrackValueSource, cancellationToken, LogLevel.Information); + var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); + if (client != null) + { + var solution = document.Project.Solution; + + var result = await client.TryInvokeAsync>( + solution, + (service, solutionInfo, cancellationToken) => service.TrackValueSourceAsync(solutionInfo, selection, document.Id, cancellationToken), + cancellationToken).ConfigureAwait(false); + + if (!result.HasValue) + { + return ImmutableArray.Empty; + } + + using var _ = PooledObjects.ArrayBuilder.GetInstance(out var builder); + + foreach (var item in result.Value) + { + var rehydratedItem = await item.RehydrateAsync(document.Project.Solution, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(rehydratedItem); + builder.Add(rehydratedItem); + } + + return builder.ToImmutable(); + } + + var progressTracker = new ValueTrackingProgressCollector(); + await ValueTracker.TrackValueSourceAsync(selection, document, progressTracker, cancellationToken).ConfigureAwait(false); + return progressTracker.GetItems(); + } + + public async Task> TrackValueSourceAsync( + Solution solution, + ValueTrackedItem previousTrackedItem, + CancellationToken cancellationToken) + { + using var logger = Logger.LogBlock(FunctionId.ValueTracking_TrackValueSource, cancellationToken, LogLevel.Information); + var project = solution.GetRequiredProject(previousTrackedItem.DocumentId.ProjectId); + var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); + if (client != null) + { + var dehydratedItem = SerializableValueTrackedItem.Dehydrate(solution, previousTrackedItem, cancellationToken); + var result = await client.TryInvokeAsync>( + solution, + (service, solutionInfo, cancellationToken) => service.TrackValueSourceAsync(solutionInfo, dehydratedItem, cancellationToken), + cancellationToken).ConfigureAwait(false); + + if (!result.HasValue) + { + return ImmutableArray.Empty; + } + + using var _ = PooledObjects.ArrayBuilder.GetInstance(out var builder); + + foreach (var item in result.Value) + { + var rehydratedItem = await item.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (rehydratedItem is null) + { + throw new InvalidOperationException(); + } + + builder.Add(rehydratedItem); + } + + return builder.ToImmutable(); + } + + var progressTracker = new ValueTrackingProgressCollector(); + await ValueTracker.TrackValueSourceAsync(solution, previousTrackedItem, progressTracker, cancellationToken).ConfigureAwait(false); + return progressTracker.GetItems(); + } + } +} diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index e4de0f1091164..e59ec843df733 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -79,7 +79,7 @@ public Solution GetCompileTimeSolution(Solution designTimeSolution) { var anyConfigs = false; - foreach (var configState in projectState.AnalyzerConfigDocumentStates.States) + foreach (var (_, configState) in projectState.AnalyzerConfigDocumentStates.States) { if (IsRazorAnalyzerConfig(configState)) { @@ -91,7 +91,7 @@ public Solution GetCompileTimeSolution(Solution designTimeSolution) // only remove design-time only documents when source-generated ones replace them if (anyConfigs) { - foreach (var documentState in projectState.DocumentStates.States) + foreach (var (_, documentState) in projectState.DocumentStates.States) { if (documentState.Attributes.DesignTimeOnly) { diff --git a/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs b/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs index 0121f4f09e907..ed90a6a80e861 100644 --- a/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs +++ b/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs @@ -93,9 +93,9 @@ private class ImplicitCacheMonitor : IdleProcessor private readonly ProjectCacheService _owner; private readonly SemaphoreSlim _gate; - public ImplicitCacheMonitor(ProjectCacheService owner, int backOffTimeSpanInMS) + public ImplicitCacheMonitor(ProjectCacheService owner, TimeSpan backOffTimeSpan) : base(AsynchronousOperationListenerProvider.NullListener, - backOffTimeSpanInMS, + backOffTimeSpan, CancellationToken.None) { _owner = owner; @@ -106,7 +106,7 @@ public ImplicitCacheMonitor(ProjectCacheService owner, int backOffTimeSpanInMS) protected override Task ExecuteAsync() { - _owner.ClearExpiredImplicitCache(DateTime.UtcNow - TimeSpan.FromMilliseconds(BackOffTimeSpanInMS)); + _owner.ClearExpiredImplicitCache(DateTime.UtcNow - BackOffTimeSpan); return Task.CompletedTask; } diff --git a/src/Features/Core/Portable/Workspace/ProjectCacheService.cs b/src/Features/Core/Portable/Workspace/ProjectCacheService.cs index 555fffd75022e..dffbc15cc5c51 100644 --- a/src/Features/Core/Portable/Workspace/ProjectCacheService.cs +++ b/src/Features/Core/Portable/Workspace/ProjectCacheService.cs @@ -34,7 +34,7 @@ internal partial class ProjectCacheService : IProjectCacheHostService public ProjectCacheService(Workspace workspace) => _workspace = workspace; - public ProjectCacheService(Workspace workspace, int implicitCacheTimeout) + public ProjectCacheService(Workspace workspace, TimeSpan implicitCacheTimeout) { _workspace = workspace; diff --git a/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs index b607e5ca7927c..364b2d7e942a5 100644 --- a/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Text.Adornments; @@ -72,6 +73,7 @@ public FindUsagesLSPContext( Document document, int position, IMetadataAsSourceFileService metadataAsSourceFileService, + IAsynchronousOperationListener asyncListener, CancellationToken cancellationToken) { _progress = progress; @@ -79,7 +81,7 @@ public FindUsagesLSPContext( _position = position; _metadataAsSourceFileService = metadataAsSourceFileService; _workQueue = new AsyncBatchingWorkQueue( - TimeSpan.FromMilliseconds(500), ReportReferencesAsync, cancellationToken); + TimeSpan.FromMilliseconds(500), ReportReferencesAsync, asyncListener, cancellationToken); } // After all definitions/references have been found, wait here until all results have been reported. @@ -336,11 +338,11 @@ static ClassifiedTextRun[] GetClassifiedTextRuns( } } - private Task ReportReferencesAsync(ImmutableArray referencesToReport, CancellationToken cancellationToken) + private ValueTask ReportReferencesAsync(ImmutableArray referencesToReport, CancellationToken cancellationToken) { // We can report outside of the lock here since _progress is thread-safe. _progress.Report(referencesToReport.ToArray()); - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; } } } diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index e0092066fe773..f149a00bbba7e 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -88,24 +88,31 @@ private static ImmutableArray FilterDocumentsByClientName(ImmutableArr return documents.FindDocumentInProjectContext(documentIdentifier); } - public static T FindDocumentInProjectContext(this ImmutableArray documents, TextDocumentIdentifier documentIdentifier) where T : TextDocument + public static Document FindDocumentInProjectContext(this ImmutableArray documents, TextDocumentIdentifier documentIdentifier) { if (documents.Length > 1) { // We have more than one document; try to find the one that matches the right context - if (documentIdentifier is VSTextDocumentIdentifier vsDocumentIdentifier) + if (documentIdentifier is VSTextDocumentIdentifier vsDocumentIdentifier && vsDocumentIdentifier.ProjectContext != null) { - if (vsDocumentIdentifier.ProjectContext != null) - { - var projectId = ProtocolConversions.ProjectContextToProjectId(vsDocumentIdentifier.ProjectContext); - var matchingDocument = documents.FirstOrDefault(d => d.Project.Id == projectId); + var projectId = ProtocolConversions.ProjectContextToProjectId(vsDocumentIdentifier.ProjectContext); + var matchingDocument = documents.FirstOrDefault(d => d.Project.Id == projectId); - if (matchingDocument != null) - { - return matchingDocument; - } + if (matchingDocument != null) + { + return matchingDocument; } } + else + { + // We were not passed a project context. This can happen when the LSP powered NavBar is not enabled. + // This branch should be removed when we're using the LSP based navbar in all scenarios. + + var solution = documents.First().Project.Solution; + // Lookup which of the linked documents is currently active in the workspace. + var documentIdInCurrentContext = solution.Workspace.GetDocumentIdInCurrentContext(documents.First().Id); + return solution.GetRequiredDocument(documentIdInCurrentContext); + } } // We either have only one document or have multiple, but none of them matched our context. In the @@ -120,7 +127,7 @@ public static async Task GetPositionFromLinePositionAsync(this TextDocument return text.Lines.GetPosition(linePosition); } - public static bool HasVisualStudioLspCapability(this ClientCapabilities clientCapabilities) + public static bool HasVisualStudioLspCapability(this ClientCapabilities? clientCapabilities) { if (clientCapabilities is VSClientCapabilities vsClientCapabilities) { diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index 0b463c186d80b..d04e255b610b0 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -84,12 +84,12 @@ internal static class ProtocolConversions int position, CancellationToken cancellationToken) { - if (context == null) + if (context is null) { // Some LSP clients don't support sending extra context, so all we can do is invoke return Completion.CompletionTrigger.Invoke; } - else if (context.TriggerKind == LSP.CompletionTriggerKind.Invoked) + else if (context.TriggerKind is LSP.CompletionTriggerKind.Invoked or LSP.CompletionTriggerKind.TriggerForIncompleteCompletions) { if (context is not LSP.VSCompletionContext vsCompletionContext) { @@ -116,7 +116,7 @@ internal static class ProtocolConversions return Completion.CompletionTrigger.Invoke; } } - else if (context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter) + else if (context.TriggerKind is LSP.CompletionTriggerKind.TriggerCharacter) { Contract.ThrowIfNull(context.TriggerCharacter); Contract.ThrowIfFalse(char.TryParse(context.TriggerCharacter, out var triggerChar)); @@ -640,6 +640,9 @@ public static async Task FormattingOptionsToDocumentOptionsAs } public static LSP.MarkupContent GetDocumentationMarkupContent(ImmutableArray tags, Document document, bool featureSupportsMarkdown) + => GetDocumentationMarkupContent(tags, document.Project.Language, featureSupportsMarkdown); + + public static LSP.MarkupContent GetDocumentationMarkupContent(ImmutableArray tags, string language, bool featureSupportsMarkdown) { if (!featureSupportsMarkdown) { @@ -657,7 +660,7 @@ public static LSP.MarkupContent GetDocumentationMarkupContent(ImmutableArray CSharpMarkdownLanguageName, (LanguageNames.VisualBasic) => VisualBasicMarkdownLanguageName, - _ => throw new InvalidOperationException($"{document.Project.Language} is not supported"), + _ => throw new InvalidOperationException($"{language} is not supported"), }; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs index d7273addb85c6..5478046726b99 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text.Adornments; using Roslyn.Utilities; @@ -73,27 +74,22 @@ public CompletionHandler( return null; } - var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); var completionOptions = await GetCompletionOptionsAsync(document, cancellationToken).ConfigureAwait(false); - var completionService = document.Project.LanguageServices.GetRequiredService(); - - // TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds. - // https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726 - var completionTrigger = await ProtocolConversions.LSPToRoslynCompletionTriggerAsync(request.Context, document, position, cancellationToken).ConfigureAwait(false); + var completionService = document.GetRequiredLanguageService(); + var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var list = await completionService.GetCompletionsAsync(document, position, completionTrigger, options: completionOptions, cancellationToken: cancellationToken).ConfigureAwait(false); - if (list == null || list.Items.IsEmpty || cancellationToken.IsCancellationRequested) + var completionListResult = await GetFilteredCompletionListAsync(request, documentText, document, completionOptions, completionService, cancellationToken).ConfigureAwait(false); + if (completionListResult == null) { return null; } + var (list, isIncomplete, resultId) = completionListResult.Value; + var lspVSClientCapability = context.ClientCapabilities.HasVisualStudioLspCapability() == true; var snippetsSupported = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false; var commitCharactersRuleCache = new Dictionary, string[]>(CommitCharacterArrayComparer.Instance); - // Cache the completion list so we can avoid recomputation in the resolve handler - var resultId = _completionListCache.UpdateCache(request.TextDocument, list); - // Feature flag to enable the return of TextEdits instead of InsertTexts (will increase payload size). // Flag is defined in VisualStudio\Core\Def\PackageRegistration.pkgdef. // We also check against the CompletionOption for test purposes only. @@ -102,7 +98,6 @@ public CompletionHandler( var returnTextEdits = featureFlagService.IsExperimentEnabled(WellKnownExperimentNames.LSPCompletion) || completionOptions.GetOption(CompletionOptions.ForceRoslynLSPCompletionExperiment, document.Project.Language); - SourceText? documentText = null; TextSpan? defaultSpan = null; LSP.Range? defaultRange = null; if (returnTextEdits) @@ -132,15 +127,17 @@ public CompletionHandler( { var completionItemResolveData = supportsCompletionListData ? null : completionResolveData; var lspCompletionItem = await CreateLSPCompletionItemAsync( - request, document, item, completionItemResolveData, lspVSClientCapability, completionTrigger, commitCharactersRuleCache, + request, document, item, completionItemResolveData, lspVSClientCapability, commitCharactersRuleCache, completionService, context.ClientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); lspCompletionItems.Add(lspCompletionItem); } + var completionList = new LSP.VSCompletionList { Items = lspCompletionItems.ToArray(), SuggestionMode = list.SuggestionModeItem != null, + IsIncomplete = isIncomplete, }; if (supportsCompletionListData) @@ -179,7 +176,6 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter CompletionItem item, CompletionResolveData? completionResolveData, bool supportsVSExtensions, - CompletionTrigger completionTrigger, Dictionary, string[]> commitCharacterRulesCache, CompletionService completionService, string? clientName, @@ -194,7 +190,7 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter if (supportsVSExtensions) { var vsCompletionItem = await CreateCompletionItemAsync( - request, document, item, completionResolveData, supportsVSExtensions, completionTrigger, commitCharacterRulesCache, + request, document, item, completionResolveData, supportsVSExtensions, commitCharacterRulesCache, completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId()); @@ -203,7 +199,7 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter else { var roslynCompletionItem = await CreateCompletionItemAsync( - request, document, item, completionResolveData, supportsVSExtensions, completionTrigger, commitCharacterRulesCache, + request, document, item, completionResolveData, supportsVSExtensions, commitCharacterRulesCache, completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); return roslynCompletionItem; @@ -216,7 +212,6 @@ static async Task CreateCompletionItemAsync( CompletionItem item, CompletionResolveData? completionResolveData, bool supportsVSExtensions, - CompletionTrigger completionTrigger, Dictionary, string[]> commitCharacterRulesCache, CompletionService completionService, string? clientName, @@ -242,7 +237,7 @@ static async Task CreateCompletionItemAsync( FilterText = item.FilterText, Kind = GetCompletionKind(item.Tags), Data = completionResolveData, - Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, + Preselect = ShouldItemBePreselected(item), }; // Complex text edits (e.g. override and partial method completions) are always populated in the @@ -401,6 +396,147 @@ static void PromoteCommonCommitCharactersOntoList(LSP.VSCompletionList completio } } + private async Task<(CompletionList CompletionList, bool IsIncomplete, long ResultId)?> GetFilteredCompletionListAsync( + LSP.CompletionParams request, + SourceText sourceText, + Document document, + OptionSet completionOptions, + CompletionService completionService, + CancellationToken cancellationToken) + { + var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); + var completionListSpan = completionService.GetDefaultCompletionListSpan(sourceText, position); + var completionTrigger = await ProtocolConversions.LSPToRoslynCompletionTriggerAsync(request.Context, document, position, cancellationToken).ConfigureAwait(false); + var isTriggerForIncompleteCompletions = request.Context?.TriggerKind == LSP.CompletionTriggerKind.TriggerForIncompleteCompletions; + + (CompletionList List, long ResultId)? result; + if (isTriggerForIncompleteCompletions) + { + // We don't have access to the original trigger, but we know the completion list is already present. + // It is safe to recompute with the invoked trigger as we will get all the items and filter down based on the current trigger. + var originalTrigger = new CompletionTrigger(CompletionTriggerKind.Invoke); + result = await CalculateListAsync(request, document, position, originalTrigger, completionOptions, completionService, _completionListCache, cancellationToken).ConfigureAwait(false); + } + else + { + // This is a new completion request, clear out the last result Id for incomplete results. + result = await CalculateListAsync(request, document, position, completionTrigger, completionOptions, completionService, _completionListCache, cancellationToken).ConfigureAwait(false); + } + + if (result == null) + { + return null; + } + + var resultId = result.Value.ResultId; + + var completionListMaxSize = completionOptions.GetOption(LspOptions.MaxCompletionListSize); + var (completionList, isIncomplete) = FilterCompletionList(result.Value.List, completionListMaxSize, completionListSpan, completionTrigger, sourceText, document); + + return (completionList, isIncomplete, resultId); + } + + private static async Task<(CompletionList CompletionList, long ResultId)?> CalculateListAsync( + LSP.CompletionParams request, + Document document, + int position, + CompletionTrigger completionTrigger, + OptionSet completionOptions, + CompletionService completionService, + CompletionListCache completionListCache, + CancellationToken cancellationToken) + { + var completionList = await completionService.GetCompletionsAsync(document, position, completionTrigger, options: completionOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + if (completionList == null || completionList.Items.IsEmpty) + { + return null; + } + + // Cache the completion list so we can avoid recomputation in the resolve handler + var resultId = completionListCache.UpdateCache(request.TextDocument, completionList); + + return (completionList, resultId); + } + + private static (CompletionList CompletionList, bool IsIncomplete) FilterCompletionList( + CompletionList completionList, + int completionListMaxSize, + TextSpan completionListSpan, + CompletionTrigger completionTrigger, + SourceText sourceText, + Document document) + { + var filterText = sourceText.GetSubText(completionListSpan).ToString(); + + // Use pattern matching to determine which items are most relevant out of the calculated items. + using var _ = ArrayBuilder>.GetInstance(out var matchResultsBuilder); + var index = 0; + var completionHelper = CompletionHelper.GetHelper(document); + foreach (var item in completionList.Items) + { + if (CompletionHelper.TryCreateMatchResult( + completionHelper, + item, + editorCompletionItem: null, + filterText, + completionTrigger.Kind, + GetFilterReason(completionTrigger), + recentItems: ImmutableArray.Empty, + includeMatchSpans: false, + index, + out var matchResult)) + { + matchResultsBuilder.Add(matchResult); + index++; + } + } + + // Next, we sort the list based on the pattern matching result. + matchResultsBuilder.Sort(MatchResult.SortingComparer); + + // Finally, truncate the list to 1000 items plus any preselected items that occur after the first 1000. + var filteredList = matchResultsBuilder + .Take(completionListMaxSize) + .Concat(matchResultsBuilder.Skip(completionListMaxSize).Where(match => ShouldItemBePreselected(match.RoslynCompletionItem))) + .Select(matchResult => matchResult.RoslynCompletionItem) + .ToImmutableArray(); + var newCompletionList = completionList.WithItems(filteredList); + + // Per the LSP spec, the completion list should be marked with isIncomplete = false when further insertions will + // not generate any more completion items. This means that we should be checking if the matchedResults is larger + // than the filteredList. However, the VS client has a bug where they do not properly re-trigger completion + // when a character is deleted to go from a complete list back to an incomplete list. + // For example, the following scenario. + // User types "So" -> server gives subset of items for "So" with isIncomplete = true + // User types "m" -> server gives entire set of items for "Som" with isIncomplete = false + // User deletes "m" -> client has to remember that "So" results were incomplete and re-request if the user types something else, like "n" + // + // Currently the VS client does not remember to re-request, so the completion list only ever shows items from "Som" + // so we always set the isIncomplete flag to true when the original list size (computed when no filter text was typed) is too large. + // VS bug here - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1335142 + var isIncomplete = completionList.Items.Length > newCompletionList.Items.Length; + + return (newCompletionList, isIncomplete); + + static CompletionFilterReason GetFilterReason(CompletionTrigger trigger) + { + return trigger.Kind switch + { + CompletionTriggerKind.Insertion => CompletionFilterReason.Insertion, + CompletionTriggerKind.Deletion => CompletionFilterReason.Deletion, + _ => CompletionFilterReason.Other, + }; + } + } + + private static bool ShouldItemBePreselected(CompletionItem completionItem) + { + // An item should be preselcted for LSP when the match priority is preselect and the item is hard selected. + // LSP does not support soft preselection, so we do not preselect in that scenario to avoid interfering with typing. + return completionItem.Rules.MatchPriority == MatchPriority.Preselect && completionItem.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection; + } + internal static ImmutableHashSet GetTriggerCharacters(CompletionProvider provider) { if (provider is LSPCompletionProvider lspProvider) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index c2d7c278a07c1..e694e67dabf45 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; @@ -100,7 +101,9 @@ private void OnDiagnosticsUpdated(object? sender, DiagnosticsUpdatedArgs updateA if (updateArgs.DocumentId == null) return; - lock (_documentIdToLastResultId) + // Ensure we do not clear the cached results while the handler is reading (and possibly then writing) + // to the cached results. + lock (_gate) { // Whenever we hear about changes to a document, drop the data we've stored for it. We'll recompute it as // necessary on the next request. @@ -145,7 +148,12 @@ private void OnDiagnosticsUpdated(object? sender, DiagnosticsUpdatedArgs updateA continue; } - if (DiagnosticsAreUnchanged(documentToPreviousDiagnosticParams, document)) + if (HaveDiagnosticsChanged(documentToPreviousDiagnosticParams, document, out var newResultId)) + { + context.TraceInformation($"Diagnostics were changed for document: {document.FilePath}"); + progress.Report(await ComputeAndReportCurrentDiagnosticsAsync(context, document, newResultId, cancellationToken).ConfigureAwait(false)); + } + else { context.TraceInformation($"Diagnostics were unchanged for document: {document.FilePath}"); @@ -155,11 +163,6 @@ private void OnDiagnosticsUpdated(object? sender, DiagnosticsUpdatedArgs updateA var previousParams = documentToPreviousDiagnosticParams[document]; progress.Report(CreateReport(previousParams.TextDocument, diagnostics: null, previousParams.PreviousResultId)); } - else - { - context.TraceInformation($"Diagnostics were changed for document: {document.FilePath}"); - await ComputeAndReportCurrentDiagnosticsAsync(context, progress, document, cancellationToken).ConfigureAwait(false); - } } // If we had a progress object, then we will have been reporting to that. Otherwise, take what we've been @@ -198,10 +201,10 @@ private static Dictionary GetDocumentToPreviousDiagn return result; } - private async Task ComputeAndReportCurrentDiagnosticsAsync( + private async Task ComputeAndReportCurrentDiagnosticsAsync( RequestContext context, - BufferedProgress progress, Document document, + string resultId, CancellationToken cancellationToken) { // Being asked about this document for the first time. Or being asked again and we have different @@ -229,7 +232,7 @@ private async Task ComputeAndReportCurrentDiagnosticsAsync( result.Add(ConvertDiagnostic(document, text, diagnostic)); } - progress.Report(RecordDiagnosticReport(document, result.ToArray())); + return CreateReport(ProtocolConversions.DocumentToTextDocumentIdentifier(document), result.ToArray(), resultId); } private void HandleRemovedDocuments(RequestContext context, DiagnosticParams[] previousResults, BufferedProgress progress) @@ -256,30 +259,45 @@ private void HandleRemovedDocuments(RequestContext context, DiagnosticParams[] p } } - private bool DiagnosticsAreUnchanged(Dictionary documentToPreviousDiagnosticParams, Document document) + /// + /// Returns true if diagnostics have changed since the last request and if so, + /// calculates a new resultId to use for subsequent computation and caches it. + /// + /// the resultIds the client sent us. + /// the document we are currently calculating results for. + /// the resultId to report new diagnostics with if changed. + private bool HaveDiagnosticsChanged( + Dictionary documentToPreviousDiagnosticParams, + Document document, + [NotNullWhen(true)] out string? newResultId) { + // Read and write the cached resultId to _documentIdToLastResultId in a single transaction + // to prevent in-between updates to _documentIdToLastResultId triggered by OnDiagnosticsUpdated. lock (_gate) { var workspace = document.Project.Solution.Workspace; - return documentToPreviousDiagnosticParams.TryGetValue(document, out var previousParams) && + if (documentToPreviousDiagnosticParams.TryGetValue(document, out var previousParams) && _documentIdToLastResultId.TryGetValue((workspace, document.Id), out var lastReportedResultId) && - lastReportedResultId == previousParams.PreviousResultId; - } - } + lastReportedResultId == previousParams.PreviousResultId) + { + // Our cached resultId for the document matches the resultId the client passed to us. + // This means the diagnostics have not changed and we do not need to re-compute. + newResultId = null; + return false; + } - private TReport RecordDiagnosticReport(Document document, VSDiagnostic[] diagnostics) - { - lock (_gate) - { // Keep track of the diagnostics we reported here so that we can short-circuit producing diagnostics for // the same diagnostic set in the future. Use a custom result-id per type (doc diagnostics or workspace // diagnostics) so that clients of one don't errantly call into the other. For example, a client // getting document diagnostics should not ask for workspace diagnostics with the result-ids it got for // doc-diagnostics. The two systems are different and cannot share results, or do things like report // what changed between each other. - var resultId = $"{GetType().Name}:{_nextDocumentResultId++}"; - _documentIdToLastResultId[(document.Project.Solution.Workspace, document.Id)] = resultId; - return CreateReport(ProtocolConversions.DocumentToTextDocumentIdentifier(document), diagnostics, resultId); + // + // Note that we can safely update the map before computation as any cancellation or exception + // during computation means that the client will never recieve this resultId and so cannot ask us for it. + newResultId = $"{GetType().Name}:{_nextDocumentResultId++}"; + _documentIdToLastResultId[(document.Project.Solution.Workspace, document.Id)] = newResultId; + return true; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs index 260d849f4c042..478d32ab93d02 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.QuickInfo; @@ -51,44 +53,74 @@ public HoverHandler() return null; } - var hover = await GetHoverAsync(info, document, context.ClientCapabilities, cancellationToken).ConfigureAwait(false); - return hover; + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + return await GetHoverAsync(info, text, document.Project.Language, document, context.ClientCapabilities, cancellationToken).ConfigureAwait(false); + } + + internal static async Task GetHoverAsync( + SemanticModel semanticModel, + int position, + HostLanguageServices languageServices, + CancellationToken cancellationToken) + { + Debug.Assert(semanticModel.Language == LanguageNames.CSharp || semanticModel.Language == LanguageNames.VisualBasic); - static async Task GetHoverAsync(QuickInfoItem info, Document document, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) + // Get the quick info service to compute quick info. + // This code path is only invoked for C# and VB, so we can directly cast to QuickInfoServiceWithProviders. + var quickInfoService = (QuickInfoServiceWithProviders)languageServices.GetRequiredService(); + var info = await quickInfoService.GetQuickInfoAsync(languageServices.WorkspaceServices.Workspace, semanticModel, position, cancellationToken).ConfigureAwait(false); + if (info == null) { - var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability(); + return null; + } - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (supportsVSExtensions) + var text = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + return await GetHoverAsync(info, text, semanticModel.Language, document: null, clientCapabilities: null, cancellationToken).ConfigureAwait(false); + } + + private static async Task GetHoverAsync( + QuickInfoItem info, + SourceText text, + string language, + Document? document, + ClientCapabilities? clientCapabilities, + CancellationToken cancellationToken) + { + var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability(); + + if (supportsVSExtensions) + { + var context = document != null + ? new IntellisenseQuickInfoBuilderContext(document, threadingContext: null, streamingPresenter: null) + : null; + return new VSHover { - return new VSHover - { - Range = ProtocolConversions.TextSpanToRange(info.Span, text), - Contents = new SumType, SumType[], MarkupContent>(string.Empty), - // Build the classified text without navigation actions - they are not serializable. - // TODO - Switch to markup content once it supports classifications. - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138 - RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, document, cancellationToken).ConfigureAwait(false) - }; - } - else + Range = ProtocolConversions.TextSpanToRange(info.Span, text), + Contents = new SumType, SumType[], MarkupContent>(string.Empty), + // Build the classified text without navigation actions - they are not serializable. + // TODO - Switch to markup content once it supports classifications. + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138 + RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, context, cancellationToken).ConfigureAwait(false) + }; + } + else + { + return new Hover { - return new Hover - { - Range = ProtocolConversions.TextSpanToRange(info.Span, text), - Contents = GetContents(info, document, clientCapabilities), - }; - } + Range = ProtocolConversions.TextSpanToRange(info.Span, text), + Contents = GetContents(info, language, clientCapabilities), + }; } - static MarkupContent GetContents(QuickInfoItem info, Document document, ClientCapabilities clientCapabilities) + // Local functions. + static MarkupContent GetContents(QuickInfoItem info, string language, ClientCapabilities? clientCapabilities) { var clientSupportsMarkdown = clientCapabilities?.TextDocument?.Hover?.ContentFormat.Contains(MarkupKind.Markdown) == true; // Insert line breaks in between sections to ensure we get double spacing between sections. var tags = info.Sections .SelectMany(section => section.TaggedParts.Add(new TaggedText(TextTags.LineBreak, Environment.NewLine))) .ToImmutableArray(); - return ProtocolConversions.GetDocumentationMarkupContent(tags, document, clientSupportsMarkdown); + return ProtocolConversions.GetDocumentationMarkupContent(tags, language, clientSupportsMarkdown); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs index a1d6b8f3ee1e1..86201184020b4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol; using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; @@ -22,12 +23,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler internal class FindAllReferencesHandler : AbstractStatelessRequestHandler { private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; + private readonly IAsynchronousOperationListener _asyncListener; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FindAllReferencesHandler(IMetadataAsSourceFileService metadataAsSourceFileService) + public FindAllReferencesHandler( + IMetadataAsSourceFileService metadataAsSourceFileService, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider) { _metadataAsSourceFileService = metadataAsSourceFileService; + _asyncListener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.LanguageServer); } public override string Method => LSP.Methods.TextDocumentReferencesName; @@ -54,7 +59,7 @@ public FindAllReferencesHandler(IMetadataAsSourceFileService metadataAsSourceFil ProtocolConversions.PositionToLinePosition(referenceParams.Position), cancellationToken).ConfigureAwait(false); var findUsagesContext = new FindUsagesLSPContext( - progress, document, position, _metadataAsSourceFileService, cancellationToken); + progress, document, position, _metadataAsSourceFileService, _asyncListener, cancellationToken); // Finds the references for the symbol at the specific position in the document, reporting them via streaming to the LSP client. await findUsagesService.FindReferencesAsync(document, position, findUsagesContext, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs index c269ebb7446a2..135274386d842 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs @@ -26,6 +26,7 @@ public static RequestContext Create( TextDocumentIdentifier? textDocument, string? clientName, ILspLogger _logger, + RequestTelemetryLogger telemetryLogger, ClientCapabilities clientCapabilities, ILspWorkspaceRegistrationService lspWorkspaceRegistrationService, Dictionary? solutionCache, @@ -50,7 +51,7 @@ public static RequestContext Create( // There are multiple possible solutions that we could be interested in, so we need to find the document // first and then get the solution from there. If we're not given a document, this will return the default // solution - document = FindDocument(_logger, lspWorkspaceRegistrationService, textDocument, clientName); + document = FindDocument(_logger, telemetryLogger, lspWorkspaceRegistrationService, textDocument, clientName); if (document is not null) { @@ -84,7 +85,12 @@ public static RequestContext Create( return new RequestContext(lspSolution, _logger.TraceInformation, clientCapabilities, clientName, document, documentChangeTracker); } - private static Document? FindDocument(ILspLogger logger, ILspWorkspaceRegistrationService lspWorkspaceRegistrationService, TextDocumentIdentifier textDocument, string? clientName) + private static Document? FindDocument( + ILspLogger logger, + RequestTelemetryLogger telemetryLogger, + ILspWorkspaceRegistrationService lspWorkspaceRegistrationService, + TextDocumentIdentifier textDocument, + string? clientName) { logger.TraceInformation($"Finding document corresponding to {textDocument.Uri}"); @@ -98,14 +104,7 @@ public static RequestContext Create( { var document = documents.FindDocumentInProjectContext(textDocument); logger.TraceInformation($"Found document in workspace {workspace.Kind}: {document.FilePath}"); - - Logger.Log(FunctionId.FindDocumentInWorkspace, KeyValueLogMessage.Create(LogType.Trace, m => - { - m["WorkspaceKind"] = workspace.Kind; - m["FoundInWorkspace"] = true; - m["DocumentUriHashCode"] = textDocument.Uri.GetHashCode(); - })); - + telemetryLogger.UpdateFindDocumentTelemetryData(success: true, workspace.Kind); return document; } } @@ -113,12 +112,7 @@ public static RequestContext Create( var searchedWorkspaceKinds = string.Join(";", workspaceKinds.ToImmutableAndClear()); logger.TraceWarning($"No document found after looking in {searchedWorkspaceKinds} workspaces, but request did contain a document uri"); - Logger.Log(FunctionId.FindDocumentInWorkspace, KeyValueLogMessage.Create(LogType.Trace, m => - { - m["AvailableWorkspaceKinds"] = searchedWorkspaceKinds; - m["FoundInWorkspace"] = false; - m["DocumentUriHashCode"] = textDocument.Uri.GetHashCode(); - })); + telemetryLogger.UpdateFindDocumentTelemetryData(success: false, workspaceKind: null); return null; } diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.RequestTelemetryLogger.cs b/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.RequestTelemetryLogger.cs index ac02a539e0d4a..e7eab26087ea7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.RequestTelemetryLogger.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.RequestTelemetryLogger.cs @@ -1,9 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.Internal.Log; @@ -42,10 +44,13 @@ internal class RequestTelemetryLogger : IDisposable /// private readonly ConcurrentDictionary _requestCounters; + private readonly LogAggregator _findDocumentResults; + public RequestTelemetryLogger(string serverTypeName) { _serverTypeName = serverTypeName; - _requestCounters = new ConcurrentDictionary(); + _requestCounters = new(); + _findDocumentResults = new(); // Buckets queued duration into 10ms buckets with the last bucket starting at 1000ms. // Queue times are relatively short and fall under 50ms, so tracking past 1000ms is not useful. @@ -56,7 +61,21 @@ public RequestTelemetryLogger(string serverTypeName) _requestDurationLogAggregator = new HistogramLogAggregator(bucketSize: 1, maxBucketValue: 40); } - public void UpdateTelemetryData(string methodName, TimeSpan queuedDuration, TimeSpan requestDuration, Result result) + public void UpdateFindDocumentTelemetryData(bool success, string? workspaceKind) + { + var workspaceKindTelemetryProperty = success ? workspaceKind : "Failed"; + + if (workspaceKindTelemetryProperty != null) + { + _findDocumentResults.IncreaseCount(workspaceKindTelemetryProperty); + } + } + + public void UpdateTelemetryData( + string methodName, + TimeSpan queuedDuration, + TimeSpan requestDuration, + Result result) { // Find the bucket corresponding to the queued duration and update the count of durations in that bucket. // This is not broken down per method as time in queue is not specific to an LSP method. @@ -121,6 +140,16 @@ public void Dispose() })); } + Logger.Log(FunctionId.LSP_FindDocumentInWorkspace, KeyValueLogMessage.Create(LogType.Trace, m => + { + m["server"] = _serverTypeName; + foreach (var kvp in _findDocumentResults) + { + var info = kvp.Key.ToString(); + m[info] = kvp.Value.GetCount(); + } + })); + // Clear telemetry we've published in case dispose is called multiple times. _requestCounters.Clear(); _queuedDurationLogAggregator = null; diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.cs b/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.cs index a0bb3496df8ec..ac32aae98a1ad 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestExecutionQueue.cs @@ -288,6 +288,7 @@ private RequestContext CreateRequestContext(QueueItem queueItem, out Workspace w queueItem.TextDocument, queueItem.ClientName, _logger, + _requestTelemetryLogger, queueItem.ClientCapabilities, _workspaceRegistrationService, _lspSolutionCache, diff --git a/src/Features/LanguageServer/Protocol/LspOptions.cs b/src/Features/LanguageServer/Protocol/LspOptions.cs new file mode 100644 index 0000000000000..570fc50bb5d50 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/LspOptions.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Text; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + internal static class LspOptions + { + private const string LocalRegistryPath = @"Roslyn\Internal\Lsp\"; + + /// + /// This sets the max list size we will return in response to a completion request. + /// If there are more than this many items, we will set the isIncomplete flag on the returned completion list. + /// + public static readonly Option2 MaxCompletionListSize = new(nameof(LspOptions), nameof(MaxCompletionListSize), defaultValue: 1000, + storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + nameof(MaxCompletionListSize))); + } + + [ExportOptionProvider, Shared] + internal class LspOptionsProvider : IOptionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public LspOptionsProvider() + { + } + + public ImmutableArray Options { get; } = ImmutableArray.Create(LspOptions.MaxCompletionListSize); + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index 7073c2029d2ab..0a1ea86441648 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -47,7 +47,7 @@ void M() caretLocation, customTags: new[] { PredefinedCodeRefactoringProviderNames.UseImplicitType }), priority: PriorityLevel.Low, - groupName: "Roslyn4", + groupName: "Roslyn1", applicableRange: new LSP.Range { Start = new Position { Line = 4, Character = 8 }, End = new Position { Line = 4, Character = 11 } }, diagnostics: null); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index 176087b2ee5d7..c7f9d448ed442 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -94,6 +95,33 @@ void M() AssertJsonEquals(expected, results.Items.First()); } + [Fact] + public async Task TestGetCompletionsTypingAsync() + { + var markup = +@"class A +{ + void M() + { + A{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "A", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: new string[] { "Class", "Internal" }, + request: completionParams, document: document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters, insertText: "A").ConfigureAwait(false); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + AssertJsonEquals(expected, results.Items.First()); + } + [Fact] public async Task TestGetCompletionsDoesNotIncludeUnimportedTypesAsync() { @@ -544,6 +572,585 @@ void M() Assert.All(results.Items, (item) => Assert.True(item.CommitCharacters.Length == 0)); } + [Fact] + public async Task TestLargeCompletionListIsMarkedIncompleteAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + T{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + } + + [Fact] + public async Task TestIncompleteCompletionListContainsPreselectedItemAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + class W + { + } + void M() + { + W someW = new {|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var caretLocation = locations["caret"].Single(); + + var completionParams = CreateCompletionParams( + caretLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: " ", + triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + var itemW = results.Items.Single(item => item.Label == "W"); + Assert.True(itemW.Preselect); + } + + [Fact] + public async Task TestRequestForIncompleteListIsFilteredDownAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + T{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var caretLocation = locations["caret"].Single(); + await testLspServer.OpenDocumentAsync(caretLocation.Uri); + + var completionParams = CreateCompletionParams( + caretLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + + await testLspServer.InsertTextAsync(caretLocation.Uri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "a")); + + completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "a", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.Contains("ta", results.Items.First().Label, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task TestIncompleteCompletionListFiltersWithPatternMatchingAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + T{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var caretLocation = locations["caret"].Single(); + await testLspServer.OpenDocumentAsync(caretLocation.Uri); + + var completionParams = CreateCompletionParams( + caretLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + + await testLspServer.InsertTextAsync(caretLocation.Uri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "C")); + + completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "C", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.Equal("TaiwanCalendar", results.Items.First().Label); + } + + [Fact] + public async Task TestIncompleteCompletionListWithDeletionAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + T{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var caretLocation = locations["caret"].Single(); + await testLspServer.OpenDocumentAsync(caretLocation.Uri); + + // Insert 'T' to make 'T' and trigger completion. + var completionParams = CreateCompletionParams( + caretLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + + // Insert 'ask' to make 'Task' and trigger completion. + await testLspServer.InsertTextAsync(caretLocation.Uri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "ask")); + completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "k", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.Equal("Task", results.Items.First().Label); + + // Delete 'ask' to make 'T' and trigger completion on deletion. + await testLspServer.DeleteTextAsync(caretLocation.Uri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, caretLocation.Range.End.Line, caretLocation.Range.End.Character + 3)); + completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Deletion, + triggerCharacter: "a", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + + // Insert 'i' to make 'Ti' and trigger completion. + await testLspServer.InsertTextAsync(caretLocation.Uri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "i")); + completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "i", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.Equal("Timeout", results.Items.First().Label); + } + + [Fact] + public async Task TestNewCompletionRequestDoesNotUseIncompleteListAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + T{|firstCaret:|} + } + + void M2() + { + Console.W{|secondCaret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var firstCaret = locations["firstCaret"].Single(); + await testLspServer.OpenDocumentAsync(firstCaret.Uri); + + // Make a completion request that returns an incomplete list. + var completionParams = CreateCompletionParams( + firstCaret, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + + // Make a second completion request, but not for the original incomplete list. + completionParams = CreateCompletionParams( + locations["secondCaret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "W", + triggerKind: LSP.CompletionTriggerKind.Invoked); + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.False(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.Equal("WindowHeight", results.Items.First().Label); + } + + [Fact] + public async Task TestRequestForIncompleteListWhenMissingCachedListAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + Ta{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var caretLocation = locations["caret"].Single(); + + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "a", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.Contains("ta", results.Items.First().Label, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task TestRequestForIncompleteListUsesCorrectCachedListAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M1() + { + int Taaa = 1; + T{|firstCaret:|} + } + + void M2() + { + int Saaa = 1; + {|secondCaret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var firstCaretLocation = locations["firstCaret"].Single(); + await testLspServer.OpenDocumentAsync(firstCaretLocation.Uri); + + // Create request to on insertion of 'T' + var completionParams = CreateCompletionParams( + firstCaretLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + Assert.Single(results.Items, item => item.Label == "Taaa"); + + // Insert 'S' at the second caret + var secondCaretLocation = locations["secondCaret"].Single(); + await testLspServer.InsertTextAsync(secondCaretLocation.Uri, (secondCaretLocation.Range.End.Line, secondCaretLocation.Range.End.Character, "S")); + + // Trigger completion on 'S' + var triggerLocation = GetLocationPlusOne(secondCaretLocation); + completionParams = CreateCompletionParams( + triggerLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "S", + triggerKind: LSP.CompletionTriggerKind.Invoked); + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("Saaa", results.Items.First().Label); + + // Now type 'a' in M1 after 'T' + await testLspServer.InsertTextAsync(firstCaretLocation.Uri, (firstCaretLocation.Range.End.Line, firstCaretLocation.Range.End.Character, "a")); + + // Trigger completion on 'a' (using incomplete as we previously returned incomplete completions from 'T'). + triggerLocation = GetLocationPlusOne(firstCaretLocation); + completionParams = CreateCompletionParams( + triggerLocation, + invokeKind: LSP.VSCompletionInvokeKind.Typing, + triggerCharacter: "a", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + + // Verify we get completions for 'Ta' and not from the 'S' location in M2 + Assert.True(results.IsIncomplete); + Assert.True(results.Items.Length < 1000); + Assert.DoesNotContain(results.Items, item => item.Label == "Saaa"); + Assert.Contains(results.Items, item => item.Label == "Taaa"); + + static LSP.Location GetLocationPlusOne(LSP.Location originalLocation) + { + var newPosition = new LSP.Position { Character = originalLocation.Range.Start.Character + 1, Line = originalLocation.Range.Start.Line }; + return new LSP.Location + { + Uri = originalLocation.Uri, + Range = new LSP.Range { Start = newPosition, End = newPosition } + }; + } + } + + [Fact] + public async Task TestCompletionRequestRespectsListSizeOptionAsync() + { + var markup = +@"class A +{ + void M() + { + {|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var listMaxSize = 1; + testLspServer.TestWorkspace.SetOptions(testLspServer.TestWorkspace.CurrentSolution.Options.WithChangedOption(LspOptions.MaxCompletionListSize, listMaxSize)); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.Equal(listMaxSize, results.Items.Length); + } + private static Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index 59e6075127441..4e031801aadc8 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -7,11 +7,15 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Test.Utilities; @@ -33,7 +37,7 @@ public async Task TestNoDocumentDiagnosticsForClosedFilesWithFSAOff() var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); Assert.Empty(results); } @@ -53,7 +57,7 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff() await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, testLspServer.GetCurrentSolution().Projects.Single().Documents.Single()); + testLspServer, document.GetURI()); Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); } @@ -72,11 +76,50 @@ public async Task TestNoDocumentDiagnosticsForOpenFilesWithFSAOffIfInPushMode() await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); Assert.Empty(results.Single().Diagnostics); } + [Fact] + public async Task TestNoDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOff() + { + var markup = +@"class A {"; + using var testLspServer = CreateTestWorkspaceWithDiagnostics(markup, BackgroundAnalysisScope.OpenFilesAndProjects, DiagnosticMode.Default); + + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + await OpenDocumentAsync(testLspServer, document); + + // Ensure we get no diagnostics when feature flag is off. + var testExperimentationService = (TestExperimentationService)testLspServer.TestWorkspace.Services.GetRequiredService(); + testExperimentationService.SetExperimentOption(WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag, false); + + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); + Assert.Empty(results.Single().Diagnostics); + } + + [Fact] + public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn() + { + var markup = +@"class A {"; + using var testLspServer = CreateTestWorkspaceWithDiagnostics(markup, BackgroundAnalysisScope.OpenFilesAndProjects, DiagnosticMode.Default); + + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + await OpenDocumentAsync(testLspServer, document); + + var testExperimentationService = (TestExperimentationService)testLspServer.TestWorkspace.Services.GetRequiredService(); + testExperimentationService.SetExperimentOption(WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag, true); + + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + } + [Fact] public async Task TestDocumentDiagnosticsForRemovedDocument() { @@ -98,7 +141,7 @@ public async Task TestDocumentDiagnosticsForRemovedDocument() await WaitForDiagnosticsAsync(workspace); var results = await testLspServer.ExecuteRequestAsync( MSLSPMethods.DocumentPullDiagnosticName, - CreateDocumentDiagnosticParams(document), + CreateDocumentDiagnosticParams(document.GetURI()), new LSP.ClientCapabilities(), clientName: null, CancellationToken.None); @@ -136,13 +179,13 @@ public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice() await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); var resultId = results.Single().ResultId; results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(), previousResultId: resultId); + testLspServer, document.GetURI(), previousResultId: resultId); Assert.Null(results.Single().Diagnostics); Assert.Equal(resultId, results.Single().ResultId); @@ -162,13 +205,13 @@ public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed() await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "}"); - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, testLspServer.GetCurrentSolution().Projects.Single().Documents.Single()); + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); Assert.Empty(results[0].Diagnostics); } @@ -186,7 +229,7 @@ public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed() var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI()); Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); @@ -195,7 +238,7 @@ public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed() await InsertTextAsync(testLspServer, document, position: 0, text: " "); results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(), + testLspServer, document.GetURI(), previousResultId: results[0].ResultId); Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); @@ -211,61 +254,12 @@ private static async Task InsertTextAsync( var sourceText = await document.GetTextAsync(); var lineInfo = sourceText.Lines.GetLinePositionSpan(new TextSpan(position, 0)); - await testLspServer.ExecuteRequestAsync( - Methods.TextDocumentDidChangeName, - new DidChangeTextDocumentParams - { - TextDocument = ProtocolConversions.DocumentToVersionedTextDocumentIdentifier(document), - ContentChanges = new TextDocumentContentChangeEvent[] - { - new TextDocumentContentChangeEvent - { - Range = new LSP.Range - { - Start = ProtocolConversions.LinePositionToPosition(lineInfo.Start), - End =ProtocolConversions.LinePositionToPosition(lineInfo.End), - }, - Text = text, - }, - }, - }, - new LSP.ClientCapabilities(), - clientName: null, - CancellationToken.None); + await testLspServer.InsertTextAsync(document.GetURI(), (lineInfo.Start.Line, lineInfo.Start.Character, text)); } - private static async Task OpenDocumentAsync(TestLspServer testLspServer, Document document) - { - await testLspServer.ExecuteRequestAsync( - Methods.TextDocumentDidOpenName, - new DidOpenTextDocumentParams - { - TextDocument = new TextDocumentItem - { - Uri = document.GetURI(), - Text = document.GetTextSynchronously(CancellationToken.None).ToString(), - } - }, - new LSP.ClientCapabilities(), - clientName: null, - CancellationToken.None); - } + private static Task OpenDocumentAsync(TestLspServer testLspServer, Document document) => testLspServer.OpenDocumentAsync(document.GetURI()); - private static async Task CloseDocumentAsync(TestLspServer testLspServer, Document document) - { - await testLspServer.ExecuteRequestAsync( - Methods.TextDocumentDidCloseName, - new DidCloseTextDocumentParams - { - TextDocument = new TextDocumentIdentifier - { - Uri = document.GetURI(), - } - }, - new LSP.ClientCapabilities(), - clientName: null, - CancellationToken.None); - } + private static Task CloseDocumentAsync(TestLspServer testLspServer, Document document) => testLspServer.CloseDocumentAsync(document.GetURI()); [Fact] public async Task TestStreamingDocumentDiagnostics() @@ -282,12 +276,56 @@ public async Task TestStreamingDocumentDiagnostics() await OpenDocumentAsync(testLspServer, document); var progress = BufferedProgress.Create(null); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(), progress: progress); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), progress: progress); Assert.Null(results); Assert.Equal("CS1513", progress.GetValues()!.Single().Diagnostics.Single().Code); } + [Fact] + public async Task TestDocumentDiagnosticsForOpenFilesUsesActiveContext() + { + var documentText = +@"#if ONE +class A { +#endif +class B {"; + var workspaceXml = +@$" + + {documentText} + + + {documentText} + +"; + + using var testLspServer = CreateTestWorkspaceFromXml(workspaceXml, BackgroundAnalysisScope.OpenFilesAndProjects); + + var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + // Open either of the documents via LSP, we're tracking the URI and text. + await OpenDocumentAsync(testLspServer, csproj1Document); + + // This opens all documents in the workspace and ensures buffers are created. + testLspServer.TestWorkspace.GetTestDocument(csproj1Document.Id).GetTextBuffer(); + + // Set CSProj2 as the active context and get diagnostics. + testLspServer.TestWorkspace.SetDocumentContext(csproj2Document.Id); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj2Document.GetURI()); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); + + // Set CSProj1 as the active context and get diagnostics. + testLspServer.TestWorkspace.SetDocumentContext(csproj1Document.Id); + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI()); + Assert.Equal(2, results.Single().Diagnostics!.Length); + Assert.All(results.Single().Diagnostics, d => Assert.Equal("CS1513", d.Code)); + Assert.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); + } + #endregion #region Workspace Diagnostics @@ -488,7 +526,7 @@ public async Task TestStreamingWorkspaceDiagnostics() private static async Task RunGetDocumentPullDiagnosticsAsync( TestLspServer testLspServer, - Document document, + Uri uri, string? previousResultId = null, IProgress? progress = null) { @@ -496,7 +534,7 @@ private static async Task RunGetDocumentPullDiagnosticsAsync var result = await testLspServer.ExecuteRequestAsync( MSLSPMethods.DocumentPullDiagnosticName, - CreateDocumentDiagnosticParams(document, previousResultId, progress), + CreateDocumentDiagnosticParams(uri, previousResultId, progress), new LSP.ClientCapabilities(), clientName: null, CancellationToken.None); @@ -531,13 +569,13 @@ private static async Task WaitForDiagnosticsAsync(TestWorkspace workspace) } private static DocumentDiagnosticsParams CreateDocumentDiagnosticParams( - Document document, + Uri uri, string? previousResultId = null, IProgress? progress = null) { return new DocumentDiagnosticsParams { - TextDocument = ProtocolConversions.DocumentToTextDocumentIdentifier(document), + TextDocument = new LSP.TextDocumentIdentifier { Uri = uri }, PreviousResultId = previousResultId, PartialResultToken = progress, }; @@ -555,26 +593,36 @@ private static WorkspaceDocumentDiagnosticsParams CreateWorkspaceDiagnosticParam } private TestLspServer CreateTestWorkspaceWithDiagnostics(string markup, BackgroundAnalysisScope scope, bool pullDiagnostics = true) + => CreateTestWorkspaceWithDiagnostics(markup, scope, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); + + private TestLspServer CreateTestWorkspaceWithDiagnostics(string markup, BackgroundAnalysisScope scope, DiagnosticMode mode) { var testLspServer = CreateTestLspServer(markup, out _); - InitializeDiagnostics(scope, testLspServer.TestWorkspace, pullDiagnostics); + InitializeDiagnostics(scope, testLspServer.TestWorkspace, mode); + return testLspServer; + } + + private TestLspServer CreateTestWorkspaceFromXml(string xmlMarkup, BackgroundAnalysisScope scope, bool pullDiagnostics = true) + { + var testLspServer = CreateXmlTestLspServer(xmlMarkup, out _); + InitializeDiagnostics(scope, testLspServer.TestWorkspace, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); return testLspServer; } private TestLspServer CreateTestWorkspaceWithDiagnostics(string[] markups, BackgroundAnalysisScope scope, bool pullDiagnostics = true) { var testLspServer = CreateTestLspServer(markups, out _); - InitializeDiagnostics(scope, testLspServer.TestWorkspace, pullDiagnostics); + InitializeDiagnostics(scope, testLspServer.TestWorkspace, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); return testLspServer; } - private static void InitializeDiagnostics(BackgroundAnalysisScope scope, TestWorkspace workspace, bool pullDiagnostics) + private static void InitializeDiagnostics(BackgroundAnalysisScope scope, TestWorkspace workspace, DiagnosticMode diagnosticMode) { workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( workspace.Options .WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.CSharp, scope) .WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, scope) - .WithChangedOption(InternalDiagnosticsOptions.NormalDiagnosticMode, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push))); + .WithChangedOption(InternalDiagnosticsOptions.NormalDiagnosticMode, diagnosticMode))); var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs index 80ed10165055c..f5cfb03de1ab6 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs @@ -24,10 +24,11 @@ public partial class DocumentChangesTests [Fact] public async Task LinkedDocuments_AllTracked() { + var documentText = "class C { }"; var workspaceXml = -@" +@$" - {|caret:|} + {documentText}{{|caret:|}} @@ -37,9 +38,7 @@ public async Task LinkedDocuments_AllTracked() using var testLspServer = CreateXmlTestLspServer(workspaceXml, out var locations); var caretLocation = locations["caret"].Single(); - var documentText = "class C { }"; - - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(caretLocation, documentText)); + await DidOpen(testLspServer, caretLocation.Uri); var trackedDocuments = testLspServer.GetQueueAccessor().GetTrackedTexts(); Assert.Equal(1, trackedDocuments.Count); @@ -51,7 +50,7 @@ public async Task LinkedDocuments_AllTracked() Assert.Equal(documentText, document.GetTextSynchronously(CancellationToken.None).ToString()); } - await DidClose(testLspServer, CreateDidCloseTextDocumentParams(caretLocation)); + await DidClose(testLspServer, caretLocation.Uri); Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); } @@ -59,10 +58,18 @@ public async Task LinkedDocuments_AllTracked() [Fact] public async Task LinkedDocuments_AllTextChanged() { + var initialText = +@"class A +{ + void M() + { + {|caret:|} + } +}"; var workspaceXml = -@" +@$" - {|caret:|} + {initialText} @@ -72,14 +79,6 @@ public async Task LinkedDocuments_AllTextChanged() using var testLspServer = CreateXmlTestLspServer(workspaceXml, out var locations); var caretLocation = locations["caret"].Single(); - var initialText = -@"class A -{ - void M() - { - - } -}"; var updatedText = @"class A { @@ -89,11 +88,11 @@ void M() } }"; - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(caretLocation, initialText)); + await DidOpen(testLspServer, caretLocation.Uri); Assert.Equal(1, testLspServer.GetQueueAccessor().GetTrackedTexts().Count); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(caretLocation.Uri, (4, 8, "// hi there"))); + await DidChange(testLspServer, caretLocation.Uri, (4, 8, "// hi there")); var solution = await GetLSPSolution(testLspServer, caretLocation.Uri); @@ -102,7 +101,7 @@ void M() Assert.Equal(updatedText, document.GetTextSynchronously(CancellationToken.None).ToString()); } - await DidClose(testLspServer, CreateDidCloseTextDocumentParams(caretLocation)); + await DidClose(testLspServer, caretLocation.Uri); Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.WithFindAllReferences.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.WithFindAllReferences.cs index 8135fa864416b..a492c42a6213b 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.WithFindAllReferences.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.WithFindAllReferences.cs @@ -28,13 +28,13 @@ void M2() } }"; - var (testLspServer, locationTyped, documentText) = await GetTestLspServerAndLocationAsync(source); + var (testLspServer, locationTyped, _) = await GetTestLspServerAndLocationAsync(source); using (testLspServer) { Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); var findResults = await FindAllReferencesHandlerTests.RunFindAllReferencesAsync(testLspServer, locationTyped); Assert.Single(findResults); @@ -42,7 +42,7 @@ void M2() Assert.Equal("A", findResults[0].ContainingType); // Declare a local inside A.M() - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (5, 0, "var i = someInt + 1;\r\n"))); + await DidChange(testLspServer, locationTyped.Uri, (5, 0, "var i = someInt + 1;\r\n")); findResults = await FindAllReferencesHandlerTests.RunFindAllReferencesAsync(testLspServer, locationTyped); Assert.Equal(2, findResults.Length); @@ -51,7 +51,7 @@ void M2() Assert.Equal("M", findResults[1].ContainingMember); // Declare a field in B - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (10, 0, "int someInt = A.someInt + 1;\r\n"))); + await DidChange(testLspServer, locationTyped.Uri, (10, 0, "int someInt = A.someInt + 1;\r\n")); findResults = await FindAllReferencesHandlerTests.RunFindAllReferencesAsync(testLspServer, locationTyped); Assert.Equal(3, findResults.Length); @@ -61,7 +61,7 @@ void M2() Assert.Equal("M", findResults[1].ContainingMember); // Declare a local inside B.M2() - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (13, 0, "var j = someInt + A.someInt;\r\n"))); + await DidChange(testLspServer, locationTyped.Uri, (13, 0, "var j = someInt + A.someInt;\r\n")); findResults = await FindAllReferencesHandlerTests.RunFindAllReferencesAsync(testLspServer, locationTyped); Assert.Equal(4, findResults.Length); @@ -76,7 +76,7 @@ void M2() // the original state will have been updated by back channels (text buffer sync, file changed on disk, etc.) // This is validating that the above didn't succeed by any means except the FAR handler being passed // the updated document, so if we regress and get lucky, we still know about it. - await DidClose(testLspServer, CreateDidCloseTextDocumentParams(locationTyped)); + await DidClose(testLspServer, locationTyped.Uri); findResults = await FindAllReferencesHandlerTests.RunFindAllReferencesAsync(testLspServer, locationTyped); Assert.Single(findResults); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs index 05d4e0e74a207..1f94bddca85ab 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs @@ -43,19 +43,19 @@ void M() { Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); Assert.Single(testLspServer.GetQueueAccessor().GetTrackedTexts()); var document = testLspServer.GetQueueAccessor().GetTrackedTexts().Single(); Assert.Equal(documentText, document.ToString()); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (4, 8, "// hi there"))); + await DidChange(testLspServer, locationTyped.Uri, (4, 8, "// hi there")); document = testLspServer.GetQueueAccessor().GetTrackedTexts().Single(); Assert.Equal(expected, document.ToString()); - await DidClose(testLspServer, CreateDidCloseTextDocumentParams(locationTyped)); + await DidClose(testLspServer, locationTyped.Uri); Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); } @@ -76,7 +76,7 @@ void M() using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); var document = testLspServer.GetQueueAccessor().GetTrackedTexts().FirstOrDefault(); @@ -100,9 +100,9 @@ void M() using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); - await Assert.ThrowsAsync(() => DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText))); + await Assert.ThrowsAsync(() => DidOpen(testLspServer, locationTyped.Uri)); } } @@ -121,7 +121,7 @@ void M() using (testLspServer) { - await Assert.ThrowsAsync(() => DidClose(testLspServer, CreateDidCloseTextDocumentParams(locationTyped))); + await Assert.ThrowsAsync(() => DidClose(testLspServer, locationTyped.Uri)); } } @@ -140,7 +140,7 @@ void M() using (testLspServer) { - await Assert.ThrowsAsync(() => DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (0, 0, "goo")))); + await Assert.ThrowsAsync(() => DidChange(testLspServer, locationTyped.Uri, (0, 0, "goo"))); } } @@ -156,13 +156,13 @@ void M() } }"; - var (testLspServer, locationTyped, documentText) = await GetTestLspServerAndLocationAsync(source); + var (testLspServer, locationTyped, _) = await GetTestLspServerAndLocationAsync(source); using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); - await DidClose(testLspServer, CreateDidCloseTextDocumentParams(locationTyped)); + await DidClose(testLspServer, locationTyped.Uri); Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); } @@ -188,13 +188,13 @@ void M() } }"; - var (testLspServer, locationTyped, documentText) = await GetTestLspServerAndLocationAsync(source); + var (testLspServer, locationTyped, _) = await GetTestLspServerAndLocationAsync(source); using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (4, 8, "// hi there"))); + await DidChange(testLspServer, locationTyped.Uri, (4, 8, "// hi there")); var document = testLspServer.GetQueueAccessor().GetTrackedTexts().FirstOrDefault(); @@ -227,9 +227,9 @@ void M() using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (4, 8, "// hi there"))); + await DidChange(testLspServer, locationTyped.Uri, (4, 8, "// hi there")); var documentTextFromWorkspace = (await testLspServer.GetCurrentSolution().GetDocuments(locationTyped.Uri).Single().GetTextAsync()).ToString(); @@ -262,13 +262,13 @@ void M() } }"; - var (testLspServer, locationTyped, documentText) = await GetTestLspServerAndLocationAsync(source); + var (testLspServer, locationTyped, _) = await GetTestLspServerAndLocationAsync(source); using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (4, 8, "// hi there"), (5, 0, " // this builds on that\r\n"))); + await DidChange(testLspServer, locationTyped.Uri, (4, 8, "// hi there"), (5, 0, " // this builds on that\r\n")); var document = testLspServer.GetQueueAccessor().GetTrackedTexts().FirstOrDefault(); @@ -298,15 +298,15 @@ void M() } }"; - var (testLspServer, locationTyped, documentText) = await GetTestLspServerAndLocationAsync(source); + var (testLspServer, locationTyped, _) = await GetTestLspServerAndLocationAsync(source); using (testLspServer) { - await DidOpen(testLspServer, CreateDidOpenTextDocumentParams(locationTyped, documentText)); + await DidOpen(testLspServer, locationTyped.Uri); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (4, 8, "// hi there"))); + await DidChange(testLspServer, locationTyped.Uri, (4, 8, "// hi there")); - await DidChange(testLspServer, CreateDidChangeTextDocumentParams(locationTyped.Uri, (5, 0, " // this builds on that\r\n"))); + await DidChange(testLspServer, locationTyped.Uri, (5, 0, " // this builds on that\r\n")); var document = testLspServer.GetQueueAccessor().GetTrackedTexts().FirstOrDefault(); @@ -324,67 +324,11 @@ void M() return (testLspServer, locationTyped, documentText.ToString()); } - private static async Task DidOpen(TestLspServer testLspServer, LSP.DidOpenTextDocumentParams didOpenParams) - { - await testLspServer.ExecuteRequestAsync(Methods.TextDocumentDidOpenName, - didOpenParams, new LSP.ClientCapabilities(), null, CancellationToken.None); - } - - private static async Task DidChange(TestLspServer testLspServer, LSP.DidChangeTextDocumentParams didChangeParams) - { - await testLspServer.ExecuteRequestAsync(Methods.TextDocumentDidChangeName, - didChangeParams, new LSP.ClientCapabilities(), null, CancellationToken.None); - } + private static Task DidOpen(TestLspServer testLspServer, Uri uri) => testLspServer.OpenDocumentAsync(uri); - private static async Task DidClose(TestLspServer testLspServer, LSP.DidCloseTextDocumentParams didCloseParams) - { - await testLspServer.ExecuteRequestAsync(Methods.TextDocumentDidCloseName, - didCloseParams, new LSP.ClientCapabilities(), null, CancellationToken.None); - } - - private static LSP.DidOpenTextDocumentParams CreateDidOpenTextDocumentParams(LSP.Location location, string source) - => new LSP.DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem - { - Text = source, - Uri = location.Uri - } - }; - - private static LSP.DidChangeTextDocumentParams CreateDidChangeTextDocumentParams(Uri documentUri, params (int line, int column, string text)[] changes) - { - var changeEvents = new List(); - foreach (var change in changes) - { - changeEvents.Add(new TextDocumentContentChangeEvent - { - Text = change.text, - Range = new LSP.Range - { - Start = new Position(change.line, change.column), - End = new Position(change.line, change.column) - } - }); - } - - return new LSP.DidChangeTextDocumentParams() - { - TextDocument = new VersionedTextDocumentIdentifier - { - Uri = documentUri - }, - ContentChanges = changeEvents.ToArray() - }; - } + private static async Task DidChange(TestLspServer testLspServer, Uri uri, params (int line, int column, string text)[] changes) + => await testLspServer.InsertTextAsync(uri, changes); - private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(LSP.Location location) - => new LSP.DidCloseTextDocumentParams() - { - TextDocument = new TextDocumentIdentifier - { - Uri = location.Uri - } - }; + private static async Task DidClose(TestLspServer testLspServer, Uri uri) => await testLspServer.CloseDocumentAsync(uri); } } diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs index 75a61d2c4ffd2..0e094db7bf6ca 100644 --- a/src/Features/Lsif/Generator/Generator.cs +++ b/src/Features/Lsif/Generator/Generator.cs @@ -22,14 +22,34 @@ namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator { internal sealed class Generator { + // LSIF generator capabilities. See https://github.com/microsoft/lsif-node/blob/main/protocol/src/protocol.ts#L925 for details. + private const bool HoverProvider = true; + private const bool DeclarationProvider = false; + private const bool DefinitionProvider = true; + private const bool ReferencesProvider = true; + private const bool TypeDefinitionProvider = false; + private const bool DocumentSymbolProvider = false; + private const bool FoldingRangeProvider = true; + private const bool DiagnosticProvider = false; + private readonly ILsifJsonWriter _lsifJsonWriter; private readonly IdFactory _idFactory = new IdFactory(); - public Generator(ILsifJsonWriter lsifJsonWriter) + private Generator(ILsifJsonWriter lsifJsonWriter) { _lsifJsonWriter = lsifJsonWriter; } + public static Generator CreateAndWriteCapabilitiesVertex(ILsifJsonWriter lsifJsonWriter) + { + var generator = new Generator(lsifJsonWriter); + var capabilitiesVertex = new Capabilities(generator._idFactory, + HoverProvider, DeclarationProvider, DefinitionProvider, ReferencesProvider, + TypeDefinitionProvider, DocumentSymbolProvider, FoldingRangeProvider, DiagnosticProvider); + generator._lsifJsonWriter.Write(capabilitiesVertex); + return generator; + } + public void GenerateForCompilation(Compilation compilation, string projectPath, HostLanguageServices languageServices, OptionSet options) { var projectVertex = new Graph.LsifProject(kind: GetLanguageKind(compilation.Language), new Uri(projectPath), _idFactory); @@ -195,6 +215,21 @@ public void GenerateForCompilation(Compilation compilation, string projectPath, var referenceResultsId = symbolResultsTracker.GetResultIdForSymbol(referencedSymbol.OriginalDefinition, Methods.TextDocumentReferencesName, () => new ReferenceResult(idFactory)); lsifJsonWriter.Write(new Item(referenceResultsId.As(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory, property: "references")); } + + // Write hover information for the symbol, if edge has not already been added. + // 'textDocument/hover' edge goes from the symbol ResultSet vertex to the hover result + // See https://github.com/Microsoft/language-server-protocol/blob/main/indexFormat/specification.md#resultset for an example. + if (symbolResultsTracker.ResultSetNeedsInformationalEdgeAdded(symbolForLinkedResultSet, Methods.TextDocumentHoverName)) + { + // TODO: Can we avoid the WaitAndGetResult_CanCallOnBackground call by adding a sync method to compute hover? + var hover = HoverHandler.GetHoverAsync(semanticModel, syntaxToken.SpanStart, languageServices, CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None); + if (hover != null) + { + var hoverResult = new HoverResult(hover, idFactory); + lsifJsonWriter.Write(hoverResult); + lsifJsonWriter.Write(Edge.Create(Methods.TextDocumentHoverName, symbolForLinkedResultSetId, hoverResult.GetId(), idFactory)); + } + } } } diff --git a/src/Features/Lsif/Generator/Graph/Capabilities.cs b/src/Features/Lsif/Generator/Graph/Capabilities.cs new file mode 100644 index 0000000000000..ce8568aa73e39 --- /dev/null +++ b/src/Features/Lsif/Generator/Graph/Capabilities.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph +{ + /// + /// Represents a single Capabilities vertex for serialization. See https://github.com/microsoft/lsif-node/blob/main/protocol/src/protocol.ts#L973 for further details. + /// + internal sealed class Capabilities : Vertex + { + [JsonProperty("hoverProvider")] + public bool HoverProvider { get; } + + [JsonProperty("declarationProvider")] + public bool DeclarationProvider { get; } + + [JsonProperty("definitionProvider")] + public bool DefinitionProvider { get; } + + [JsonProperty("referencesProvider")] + public bool ReferencesProvider { get; } + + [JsonProperty("typeDefinitionProvider")] + public bool TypeDefinitionProvider { get; } + + [JsonProperty("documentSymbolProvider")] + public bool DocumentSymbolProvider { get; } + + [JsonProperty("foldingRangeProvider")] + public bool FoldingRangeProvider { get; } + + [JsonProperty("diagnosticProvider")] + public bool DiagnosticProvider { get; } + + public Capabilities( + IdFactory idFactory, + bool hoverProvider, + bool declarationProvider, + bool definitionProvider, + bool referencesProvider, + bool typeDefinitionProvider, + bool documentSymbolProvider, + bool foldingRangeProvider, + bool diagnosticProvider) + : base(label: "capabilities", idFactory) + { + HoverProvider = hoverProvider; + DeclarationProvider = declarationProvider; + DefinitionProvider = definitionProvider; + ReferencesProvider = referencesProvider; + TypeDefinitionProvider = typeDefinitionProvider; + DocumentSymbolProvider = documentSymbolProvider; + FoldingRangeProvider = foldingRangeProvider; + DiagnosticProvider = diagnosticProvider; + } + } +} diff --git a/src/Features/Lsif/Generator/Graph/HoverResult.cs b/src/Features/Lsif/Generator/Graph/HoverResult.cs new file mode 100644 index 0000000000000..5731da1bc1b6a --- /dev/null +++ b/src/Features/Lsif/Generator/Graph/HoverResult.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph +{ + /// + /// Represents a foverResult vertex for serialization. See https://github.com/Microsoft/language-server-protocol/blob/main/indexFormat/specification.md#more-about-request-textdocumenthover for further details. + /// + internal sealed class HoverResult : Vertex + { + [JsonProperty("result")] + public Hover Result { get; } + + public HoverResult(Hover result, IdFactory idFactory) + : base(label: "hoverResult", idFactory) + { + Result = result; + } + } +} diff --git a/src/Features/Lsif/Generator/Program.cs b/src/Features/Lsif/Generator/Program.cs index 2e07c00106ed6..119b1ba7e21ae 100644 --- a/src/Features/Lsif/Generator/Program.cs +++ b/src/Features/Lsif/Generator/Program.cs @@ -136,7 +136,7 @@ private static async Task GenerateWithMSBuildLocatedAsync( var solution = await openAsync(msbuildWorkspace); await logFile.WriteLineAsync($"Load completed in {solutionLoadStopwatch.Elapsed.ToDisplayString()}."); - var lsifGenerator = new Generator(lsifWriter); + var lsifGenerator = Generator.CreateAndWriteCapabilitiesVertex(lsifWriter); var totalTimeInGenerationAndCompilationFetchStopwatch = Stopwatch.StartNew(); var totalTimeInGenerationPhase = TimeSpan.Zero; @@ -173,7 +173,7 @@ private static async Task GenerateFromCompilerInvocationAsync(FileInfo compilerI await logFile.WriteLineAsync($"Load of the project completed in {compilerInvocationLoadStopwatch.Elapsed.ToDisplayString()}."); var generationStopwatch = Stopwatch.StartNew(); - var lsifGenerator = new Generator(lsifWriter); + var lsifGenerator = Generator.CreateAndWriteCapabilitiesVertex(lsifWriter); lsifGenerator.GenerateForCompilation(compilerInvocation.Compilation, compilerInvocation.ProjectFilePath, compilerInvocation.LanguageServices, compilerInvocation.Options); await logFile.WriteLineAsync($"Generation for {compilerInvocation.ProjectFilePath} completed in {generationStopwatch.Elapsed.ToDisplayString()}."); diff --git a/src/Features/Lsif/GeneratorTest/HoverTests.vb b/src/Features/Lsif/GeneratorTest/HoverTests.vb new file mode 100644 index 0000000000000..f8f4c2488e949 --- /dev/null +++ b/src/Features/Lsif/GeneratorTest/HoverTests.vb @@ -0,0 +1,168 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.LanguageServer.Protocol + +Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests + + Public NotInheritable Class HoverTests + Private Const TestProjectAssemblyName As String = "TestProject" + + + + + + + + Doc Comment + void [|M|]() { } +}")> + Public Async Function TestDefinition(code As String) As Task + Dim lsif = Await TestLsifOutput.GenerateForWorkspaceAsync( + TestWorkspace.CreateWorkspace( + + FilePath="Z:\TestProject.csproj" CommonReferences="true"> + + <%= code %> + + + )) + + Dim rangeVertex = Await lsif.GetSelectedRangeAsync() + Dim resultSetVertex = lsif.GetLinkedVertices(Of Graph.ResultSet)(rangeVertex, "next").Single() + Dim hoverVertex = lsif.GetLinkedVertices(Of Graph.HoverResult)(resultSetVertex, Methods.TextDocumentHoverName).SingleOrDefault() + Dim hoverMarkupContent = DirectCast(hoverVertex.Result.Contents.Value, MarkupContent) + + Dim expectedHoverContents As String + Select Case code + Case "class [|C|] { string s; }" + expectedHoverContents = "class C" + Case "class C { void [|M|]() { } }" + expectedHoverContents = "void C.M()" + Case "class C { string [|s|]; }" + expectedHoverContents = $"({FeaturesResources.field}) string C.s" + Case "class C { void M(string [|s|]) { M(s); } }" + expectedHoverContents = $"({FeaturesResources.parameter}) string s" + Case "class C { void M(string s) { string [|local|] = """"; } }" + expectedHoverContents = $"({FeaturesResources.local_variable}) string local" + Case " +class C +{ + /// Doc Comment + void [|M|]() { } +}" + expectedHoverContents = "void C.M() +Doc Comment" + Case Else + Throw TestExceptionUtilities.UnexpectedValue(code) + End Select + + Assert.Equal(MarkupKind.PlainText, hoverMarkupContent.Kind) + Assert.Equal(expectedHoverContents + Environment.NewLine, hoverMarkupContent.Value) + End Function + + + + + + + + + + void M() { } +}")> + Public Async Function TestReference(code As String) As Task + Dim lsif = Await TestLsifOutput.GenerateForWorkspaceAsync( + TestWorkspace.CreateWorkspace( + + FilePath="Z:\TestProject.csproj" CommonReferences="true"> + + <%= code %> + + + )) + + Dim rangeVertex = Await lsif.GetSelectedRangeAsync() + Dim resultSetVertex = lsif.GetLinkedVertices(Of Graph.ResultSet)(rangeVertex, "next").Single() + Dim hoverVertex = lsif.GetLinkedVertices(Of Graph.HoverResult)(resultSetVertex, Methods.TextDocumentHoverName).SingleOrDefault() + Dim hoverMarkupContent = DirectCast(hoverVertex.Result.Contents.Value, MarkupContent) + + Dim expectedHoverContents As String + Select Case code + Case "class C { [|string|] s; }" + expectedHoverContents = "class System.String" + Case "class C { void M() { [|M|](); } }" + expectedHoverContents = "void C.M()" + Case "class C { void M(string s) { M([|s|]); } }" + expectedHoverContents = $"({FeaturesResources.parameter}) string s" + Case "class C { void M(string s) { string local = """"; M([|local|]); } }" + expectedHoverContents = $"({FeaturesResources.local_variable}) string local" + Case "using [|S|] = System.String;" + expectedHoverContents = "class System.String" + Case "class C { [|global|]::System.String s; }" + expectedHoverContents = "" + Case " +class C +{ + /// + void M() { } +}" + expectedHoverContents = "void C.M()" + Case Else + Throw TestExceptionUtilities.UnexpectedValue(code) + End Select + + Assert.Equal(MarkupKind.PlainText, hoverMarkupContent.Kind) + Assert.Equal(expectedHoverContents + Environment.NewLine, hoverMarkupContent.Value) + End Function + + + Public Async Function ToplevelResultsInMultipleFiles() As Task + Dim lsif = Await TestLsifOutput.GenerateForWorkspaceAsync( + TestWorkspace.CreateWorkspace( + + FilePath="Z:\TestProject.csproj" CommonReferences="true"> + + class A { [|string|] s; } + + + class B { [|string|] s; } + + + class C { [|string|] s; } + + + class D { [|string|] s; } + + + class E { [|string|] s; } + + + )) + + Dim hoverVertex As Graph.HoverResult = Nothing + For Each rangeVertex In Await lsif.GetSelectedRangesAsync() + Dim resultSetVertex = lsif.GetLinkedVertices(Of Graph.ResultSet)(rangeVertex, "next").Single() + Dim vertex = lsif.GetLinkedVertices(Of Graph.HoverResult)(resultSetVertex, Methods.TextDocumentHoverName).Single() + If hoverVertex Is Nothing Then + hoverVertex = vertex + Else + Assert.Same(hoverVertex, vertex) + End If + Next + + Dim hoverMarkupContent = DirectCast(hoverVertex.Result.Contents.Value, MarkupContent) + Assert.Equal(MarkupKind.PlainText, hoverMarkupContent.Kind) + Assert.Equal("class System.String" + Environment.NewLine, hoverMarkupContent.Value) + End Function + End Class +End Namespace diff --git a/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb b/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb index c03e713c7fceb..2ba3059feca06 100644 --- a/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb +++ b/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb @@ -25,16 +25,17 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ), jsonWriter) AssertEx.EqualOrDiff( -"{""kind"":""csharp"",""resource"":""file:///Z:/TestProject.csproj"",""id"":1,""type"":""vertex"",""label"":""project""} -{""kind"":""begin"",""scope"":""project"",""data"":1,""id"":2,""type"":""vertex"",""label"":""$event""} -{""uri"":""file:///Z:/A.cs"",""languageId"":""csharp"",""id"":3,""type"":""vertex"",""label"":""document""} -{""kind"":""begin"",""scope"":""document"",""data"":3,""id"":4,""type"":""vertex"",""label"":""$event""} -{""outV"":3,""inVs"":[],""id"":5,""type"":""edge"",""label"":""contains""} -{""result"":[],""id"":6,""type"":""vertex"",""label"":""foldingRangeResult""} -{""outV"":3,""inVs"":[6],""id"":7,""type"":""edge"",""label"":""textDocument/foldingRange""} -{""kind"":""end"",""scope"":""document"",""data"":3,""id"":8,""type"":""vertex"",""label"":""$event""} -{""outV"":1,""inVs"":[3],""id"":9,""type"":""edge"",""label"":""contains""} -{""kind"":""end"",""scope"":""project"",""data"":1,""id"":10,""type"":""vertex"",""label"":""$event""} +"{""hoverProvider"":true,""declarationProvider"":false,""definitionProvider"":true,""referencesProvider"":true,""typeDefinitionProvider"":false,""documentSymbolProvider"":false,""foldingRangeProvider"":true,""diagnosticProvider"":false,""id"":1,""type"":""vertex"",""label"":""capabilities""} +{""kind"":""csharp"",""resource"":""file:///Z:/TestProject.csproj"",""id"":2,""type"":""vertex"",""label"":""project""} +{""kind"":""begin"",""scope"":""project"",""data"":2,""id"":3,""type"":""vertex"",""label"":""$event""} +{""uri"":""file:///Z:/A.cs"",""languageId"":""csharp"",""id"":4,""type"":""vertex"",""label"":""document""} +{""kind"":""begin"",""scope"":""document"",""data"":4,""id"":5,""type"":""vertex"",""label"":""$event""} +{""outV"":4,""inVs"":[],""id"":6,""type"":""edge"",""label"":""contains""} +{""result"":[],""id"":7,""type"":""vertex"",""label"":""foldingRangeResult""} +{""outV"":4,""inVs"":[7],""id"":8,""type"":""edge"",""label"":""textDocument/foldingRange""} +{""kind"":""end"",""scope"":""document"",""data"":4,""id"":9,""type"":""vertex"",""label"":""$event""} +{""outV"":2,""inVs"":[4],""id"":10,""type"":""edge"",""label"":""contains""} +{""kind"":""end"",""scope"":""project"",""data"":2,""id"":11,""type"":""vertex"",""label"":""$event""} ", stringWriter.ToString()) End Function @@ -54,80 +55,93 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests AssertEx.EqualOrDiff( "[ + { + ""hoverProvider"": true, + ""declarationProvider"": false, + ""definitionProvider"": true, + ""referencesProvider"": true, + ""typeDefinitionProvider"": false, + ""documentSymbolProvider"": false, + ""foldingRangeProvider"": true, + ""diagnosticProvider"": false, + ""id"": 1, + ""type"": ""vertex"", + ""label"": ""capabilities"" + }, { ""kind"": ""csharp"", ""resource"": ""file:///Z:/TestProject.csproj"", - ""id"": 1, + ""id"": 2, ""type"": ""vertex"", ""label"": ""project"" }, { ""kind"": ""begin"", ""scope"": ""project"", - ""data"": 1, - ""id"": 2, + ""data"": 2, + ""id"": 3, ""type"": ""vertex"", ""label"": ""$event"" }, { ""uri"": ""file:///Z:/A.cs"", ""languageId"": ""csharp"", - ""id"": 3, + ""id"": 4, ""type"": ""vertex"", ""label"": ""document"" }, { ""kind"": ""begin"", ""scope"": ""document"", - ""data"": 3, - ""id"": 4, + ""data"": 4, + ""id"": 5, ""type"": ""vertex"", ""label"": ""$event"" }, { - ""outV"": 3, + ""outV"": 4, ""inVs"": [], - ""id"": 5, + ""id"": 6, ""type"": ""edge"", ""label"": ""contains"" }, { ""result"": [], - ""id"": 6, + ""id"": 7, ""type"": ""vertex"", ""label"": ""foldingRangeResult"" }, { - ""outV"": 3, + ""outV"": 4, ""inVs"": [ - 6 + 7 ], - ""id"": 7, + ""id"": 8, ""type"": ""edge"", ""label"": ""textDocument/foldingRange"" }, { ""kind"": ""end"", ""scope"": ""document"", - ""data"": 3, - ""id"": 8, + ""data"": 4, + ""id"": 9, ""type"": ""vertex"", ""label"": ""$event"" }, { - ""outV"": 1, + ""outV"": 2, ""inVs"": [ - 3 + 4 ], - ""id"": 9, + ""id"": 10, ""type"": ""edge"", ""label"": ""contains"" }, { ""kind"": ""end"", ""scope"": ""project"", - ""data"": 1, - ""id"": 10, + ""data"": 2, + ""id"": 11, ""type"": ""vertex"", ""label"": ""$event"" } diff --git a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb index 3e8f98eab3b61..1c3fe9031bc43 100644 --- a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb +++ b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb @@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U End Function Public Shared Async Function GenerateForWorkspaceAsync(workspace As TestWorkspace, jsonWriter As ILsifJsonWriter) As Task - Dim generator = New Generator(jsonWriter) + Dim lsifGenerator = Generator.CreateAndWriteCapabilitiesVertex(jsonWriter) For Each project In workspace.CurrentSolution.Projects Dim compilation = Await project.GetCompilationAsync() @@ -35,7 +35,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U ' Assert we don't have any errors to prevent any typos in the tests Assert.Empty(compilation.GetDiagnostics().Where(Function(d) d.Severity = DiagnosticSeverity.Error)) - generator.GenerateForCompilation(compilation, project.FilePath, project.LanguageServices, project.Solution.Options) + lsifGenerator.GenerateForCompilation(compilation, project.FilePath, project.LanguageServices, project.Solution.Options) Next End Function diff --git a/src/Features/VisualBasic/Portable/AddImport/VisualBasicAddImportFeatureService.vb b/src/Features/VisualBasic/Portable/AddImport/VisualBasicAddImportFeatureService.vb index 2ce69719b41d8..fe2a16b3541e2 100644 --- a/src/Features/VisualBasic/Portable/AddImport/VisualBasicAddImportFeatureService.vb +++ b/src/Features/VisualBasic/Portable/AddImport/VisualBasicAddImportFeatureService.vb @@ -47,12 +47,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport If parent Is Nothing Then Return False End If + Dim method = TryCast(parent.Expression, MemberAccessExpressionSyntax) If method IsNot Nothing Then node = method.Name Else node = parent.Expression End If + Exit Select Case AddImportDiagnosticIds.BC36719 If node.IsKind(SyntaxKind.ObjectCollectionInitializer) Then @@ -171,8 +173,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport If simpleName IsNot Nothing Then Return simpleName End If + qn = TryCast(left, QualifiedNameSyntax) End While + Return Nothing End Function diff --git a/src/Features/VisualBasic/Portable/CodeFixes/CorrectNextControlVariable/CorrectNextControlVariableCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/CorrectNextControlVariable/CorrectNextControlVariableCodeFixProvider.vb index 8a0aca04db918..f568245302b73 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/CorrectNextControlVariable/CorrectNextControlVariableCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/CorrectNextControlVariable/CorrectNextControlVariableCodeFixProvider.vb @@ -82,6 +82,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.CorrectNextControlVariabl If forBlock Is Nothing Then Return Nothing End If + currentNode = forBlock Next diff --git a/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb index ddd61807cdd2a..0dcdf827df55e 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb @@ -43,6 +43,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression If SyntaxFacts.GetKeywordKind(text) <> SyntaxKind.None Then text = "[" & text & "]" End If + Return New SeparatedSyntaxList(Of IdentifierNameSyntax)().Add(SyntaxFactory.IdentifierName(text)) End Function diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/InlineMethod/VisualBasicInlineMethodRefactoringProvider.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/InlineMethod/VisualBasicInlineMethodRefactoringProvider.vb index 7f318aaa21caf..dff559e23541a 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/InlineMethod/VisualBasicInlineMethodRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/InlineMethod/VisualBasicInlineMethodRefactoringProvider.vb @@ -42,6 +42,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.InlineTemporary Return throwStatement.Expression End If End If + Return Nothing End Function diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.vb index 06441b73d0fa9..58aa2cf2e696f 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/InlineTemporary/InlineTemporaryCodeRefactoringProvider.vb @@ -463,6 +463,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.InlineTemporary Return newNode _ .WithAdditionalAnnotations(ConflictAnnotation.Create(VBFeaturesResources.Conflict_s_detected)) End Function + Return Await inlinedDocument.ReplaceNodesAsync(replacementNodesWithChangedSemantics.Keys, conflictAnnotationAdder, cancellationToken).ConfigureAwait(False) End Function diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImplementsClauseCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImplementsClauseCompletionProvider.vb index 61c05e4ef14da..36c327ab2e112 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImplementsClauseCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImplementsClauseCompletionProvider.vb @@ -65,10 +65,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers If methodDeclaration IsNot Nothing Then memberKindKeyword = methodDeclaration.DeclarationKeyword.Kind End If + Dim propertyDeclaration = context.TargetToken.GetAncestor(Of PropertyStatementSyntax)() If propertyDeclaration IsNot Nothing Then memberKindKeyword = propertyDeclaration.DeclarationKeyword.Kind End If + Dim eventDeclaration = context.TargetToken.GetAncestor(Of EventStatementSyntax)() If eventDeclaration IsNot Nothing Then memberKindKeyword = eventDeclaration.DeclarationKeyword.Kind @@ -115,6 +117,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers If Not method.ReturnsVoid Then Return memberKindKeyword = SyntaxKind.FunctionKeyword End If + Return memberKindKeyword = SyntaxKind.SubKeyword End If @@ -164,6 +167,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers If container Is Nothing Then Return ImmutableArray(Of ISymbol).Empty End If + Dim symbols = semanticModel.LookupSymbols(position, container) Dim hashSet = New HashSet(Of ISymbol)(symbols.ToArray() _ @@ -208,6 +212,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers defaultListing.Add(containingType.ContainingNamespace) AddAliasesAndContainers(containingType.ContainingNamespace, defaultListing, node, semanticModel) End If + Return defaultListing.ToImmutableArray() End If diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb index 5afcb1cb1455e..c090b7c6f9165 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb @@ -230,6 +230,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Else change = New TextChange(symbolItem.Span, insertionText) End If + Return Task.FromResult(Of TextChange?)(change) End Function End Class diff --git a/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/EndBlockKeywordRecommender.vb b/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/EndBlockKeywordRecommender.vb index 28d0cefe6100a..d7a549d7e754d 100644 --- a/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/EndBlockKeywordRecommender.vb +++ b/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/EndBlockKeywordRecommender.vb @@ -88,6 +88,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.KeywordRecommenders.Decl collection.Add(item2) End If Next + Exit For End If Next diff --git a/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/GenericConstraintsKeywordRecommender.vb b/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/GenericConstraintsKeywordRecommender.vb index c840aa64b4921..c91fc6142b9aa 100644 --- a/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/GenericConstraintsKeywordRecommender.vb +++ b/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/Declarations/GenericConstraintsKeywordRecommender.vb @@ -37,9 +37,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.KeywordRecommenders.Decl If previousConstraints.Any(Function(constraint) Not constraint.IsKind(SyntaxKind.TypeConstraint)) Then recommendations.RemoveAll(Function(k) k.Keyword = "Structure") End If + If previousConstraints.Any(Function(constraint) constraint.IsKind(SyntaxKind.ClassConstraint, SyntaxKind.StructureConstraint)) Then recommendations.RemoveAll(Function(k) k.Keyword = "Class") End If + If previousConstraints.Any(Function(constraint) constraint.IsKind(SyntaxKind.NewConstraint, SyntaxKind.StructureConstraint)) Then recommendations.RemoveAll(Function(k) k.Keyword = "New") End If diff --git a/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/PreprocessorDirectives/PreprocessorHelpers.vb b/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/PreprocessorDirectives/PreprocessorHelpers.vb index 9d68f26049a40..4ebea42caca39 100644 --- a/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/PreprocessorDirectives/PreprocessorHelpers.vb +++ b/src/Features/VisualBasic/Portable/Completion/KeywordRecommenders/PreprocessorDirectives/PreprocessorHelpers.vb @@ -45,6 +45,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.KeywordRecommenders.Prep If child.FullSpan.Start > _maxPosition Then Exit For End If + If child.IsNode Then Visit(child.AsNode()) End If diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/SyntaxComparer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/SyntaxComparer.vb index 81081ccd2928b..8eaf50ee137bf 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/SyntaxComparer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/SyntaxComparer.vb @@ -871,6 +871,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue isLeaf = False Return Label.AttributeList End If + isLeaf = True Return Label.Ignored @@ -879,6 +880,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If nodeOpt IsNot Nothing AndAlso nodeOpt.Parent.IsParentKind(SyntaxKind.AttributesStatement) Then Return Label.Attribute End If + Return Label.Ignored Case Else diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 0dcaaea8c095b..75eab22cc8d3b 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1280,16 +1280,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue node = node.Parent ' for attributes on return types of functions End If - Case SyntaxKind.EventStatement - If node.Parent.IsKind(SyntaxKind.EventBlock) Then - Return False - End If - - Case SyntaxKind.PropertyStatement ' autoprop or interface property - If node.Parent.IsKind(SyntaxKind.PropertyBlock) Then - Return False - End If - Case SyntaxKind.Parameter If editKind = EditKind.Update Then ' for delegate invoke methods we need to go one step higher, to the delegate itself @@ -1302,6 +1292,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue symbols = OneOrMany.Create(parameterSymbol) Return True End If + Return False Case SyntaxKind.TypeParameter @@ -1328,6 +1319,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue symbols = OneOrMany.Create(variableDeclarator.Names.SelectAsArray(Function(n) model.GetDeclaredSymbol(n, cancellationToken))) Return True End If + node = variableDeclarator.Names(0) Case SyntaxKind.FieldDeclaration @@ -2284,6 +2276,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If node.Parent.IsParentKind(SyntaxKind.AttributesStatement) Then ReportError(RudeEditKind.Insert) End If + Return Case SyntaxKind.AttributeList @@ -2291,6 +2284,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If node.IsParentKind(SyntaxKind.AttributesStatement) Then ReportError(RudeEditKind.Insert) End If + Return Case SyntaxKind.ParameterList @@ -2366,6 +2360,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If oldNode.IsParentKind(SyntaxKind.AttributesStatement) Then ReportError(RudeEditKind.Insert) End If + Return Case SyntaxKind.Attribute @@ -2373,6 +2368,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If oldNode.Parent.IsParentKind(SyntaxKind.AttributesStatement) Then ReportError(RudeEditKind.Insert) End If + Return Case SyntaxKind.TypeParameter, @@ -2456,7 +2452,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Case SyntaxKind.FieldDeclaration - ClassifyUpdate(DirectCast(oldNode, FieldDeclarationSyntax), DirectCast(newNode, FieldDeclarationSyntax)) Return Case SyntaxKind.VariableDeclarator @@ -2499,7 +2494,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Case SyntaxKind.SubNewStatement - ClassifyUpdate(DirectCast(oldNode, SubNewStatementSyntax), DirectCast(newNode, SubNewStatementSyntax)) Return Case SyntaxKind.PropertyBlock @@ -2568,6 +2562,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If newNode.Parent.IsParentKind(SyntaxKind.AttributesStatement) Then ReportError(RudeEditKind.Update) End If + Return Case SyntaxKind.AttributeList, @@ -2586,16 +2581,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As TypeStatementSyntax, newNode As TypeStatementSyntax) - If oldNode.RawKind <> newNode.RawKind Then - ReportError(RudeEditKind.TypeKindUpdate) - Return - End If - - If Not AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore:=SyntaxKind.PartialKeyword) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) End If @@ -2616,11 +2601,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return End If - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - If Not SyntaxFactory.AreEquivalent(oldNode.UnderlyingType, newNode.UnderlyingType) Then ReportError(RudeEditKind.EnumUnderlyingTypeUpdate) Return @@ -2628,11 +2608,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As DelegateStatementSyntax, newNode As DelegateStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - ' Function changed to Sub or vice versa. Note that Function doesn't need to have AsClause. If oldNode.RawKind <> newNode.RawKind Then ReportError(RudeEditKind.TypeUpdate) @@ -2650,14 +2625,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Sub ClassifyUpdate(oldNode As FieldDeclarationSyntax, newNode As FieldDeclarationSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - End If - - ' VariableDeclarator separators were modified - End Sub - Private Sub ClassifyUpdate(oldNode As ModifiedIdentifierSyntax, newNode As ModifiedIdentifierSyntax) If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) @@ -2709,12 +2676,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As PropertyStatementSyntax, newNode As PropertyStatementSyntax) - If Not IncludesSignificantPropertyModifiers(oldNode.Modifiers, newNode.Modifiers) OrElse - Not IncludesSignificantPropertyModifiers(newNode.Modifiers, oldNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) Return @@ -2735,23 +2696,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Shared Function IncludesSignificantPropertyModifiers(subset As SyntaxTokenList, superset As SyntaxTokenList) As Boolean - For Each modifier In subset - ' ReadOnly and WriteOnly keywords are redundant, it would be a semantic error if they Then didn't match the present accessors. - ' We want to allow adding an accessor to a property, which requires change in the RO/WO modifiers. - If modifier.IsKind(SyntaxKind.ReadOnlyKeyword) OrElse - modifier.IsKind(SyntaxKind.WriteOnlyKeyword) Then - Continue For - End If - - If Not superset.Any(modifier.Kind) Then - Return False - End If - Next - - Return True - End Function - ' Returns true if the initializer has changed. Private Function ClassifyTypeAndInitializerUpdates(oldEqualsValue As EqualsValueSyntax, oldClause As AsClauseSyntax, @@ -2773,10 +2717,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ' A custom event can't be matched with a field event and vice versa: Debug.Assert(SyntaxFactory.AreEquivalent(oldNode.CustomKeyword, newNode.CustomKeyword)) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - End If - If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) Return @@ -2790,8 +2730,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Dim oldHasGeneratedType = oldNode.ParameterList IsNot Nothing Dim newHasGeneratedType = newNode.ParameterList IsNot Nothing - Debug.Assert(oldHasGeneratedType <> newHasGeneratedType) - ReportError(RudeEditKind.TypeUpdate) + If oldHasGeneratedType <> newHasGeneratedType Then + ReportError(RudeEditKind.TypeUpdate) + End If End Sub Private Sub ClassifyUpdate(oldNode As MethodBlockSyntax, newNode As MethodBlockSyntax) @@ -2812,11 +2753,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return End If - If Not ClassifyMethodModifierUpdate(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - ' TODO (tomat): We can support this If Not SyntaxFactory.AreEquivalent(oldNode.HandlesClause, newNode.HandlesClause) Then ReportError(RudeEditKind.HandlesClauseUpdate) @@ -2829,35 +2765,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Shared Function ClassifyMethodModifierUpdate(oldModifiers As SyntaxTokenList, newModifiers As SyntaxTokenList) As Boolean - ' Ignore Async and Iterator keywords when matching modifiers. - ' State machine checks are done in ComputeBodyMatch. - - Dim oldAsyncIndex = oldModifiers.IndexOf(SyntaxKind.AsyncKeyword) - Dim newAsyncIndex = newModifiers.IndexOf(SyntaxKind.AsyncKeyword) - - If oldAsyncIndex >= 0 Then - oldModifiers = oldModifiers.RemoveAt(oldAsyncIndex) - End If - - If newAsyncIndex >= 0 Then - newModifiers = newModifiers.RemoveAt(newAsyncIndex) - End If - - Dim oldIteratorIndex = oldModifiers.IndexOf(SyntaxKind.IteratorKeyword) - Dim newIteratorIndex = newModifiers.IndexOf(SyntaxKind.IteratorKeyword) - - If oldIteratorIndex >= 0 Then - oldModifiers = oldModifiers.RemoveAt(oldIteratorIndex) - End If - - If newIteratorIndex >= 0 Then - newModifiers = newModifiers.RemoveAt(newIteratorIndex) - End If - - Return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers) - End Function - Private Sub ClassifyUpdate(oldNode As DeclareStatementSyntax, newNode As DeclareStatementSyntax) If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) @@ -2891,13 +2798,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As OperatorStatementSyntax, newNode As OperatorStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then + Dim oldWidening = oldNode.Modifiers.IndexOf(SyntaxKind.WideningKeyword) >= 0 + Dim oldNarrowing = oldNode.Modifiers.IndexOf(SyntaxKind.NarrowingKeyword) >= 0 + Dim newWidening = newNode.Modifiers.IndexOf(SyntaxKind.WideningKeyword) >= 0 + Dim newNarrowing = newNode.Modifiers.IndexOf(SyntaxKind.NarrowingKeyword) >= 0 + + If newWidening <> oldWidening OrElse newNarrowing <> oldNarrowing Then ReportError(RudeEditKind.ModifiersUpdate) Return End If - - Debug.Assert(Not SyntaxFactory.AreEquivalent(oldNode.OperatorToken, newNode.OperatorToken)) - ReportError(RudeEditKind.Renamed) End Sub Private Sub ClassifyUpdate(oldNode As AccessorBlockSyntax, newNode As AccessorBlockSyntax) @@ -2929,13 +2838,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue containingType:=DirectCast(newNode.Parent, TypeBlockSyntax)) End Sub - Private Sub ClassifyUpdate(oldNode As SubNewStatementSyntax, newNode As SubNewStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - End Sub - Private Sub ClassifyUpdate(oldNode As SimpleAsClauseSyntax, newNode As SimpleAsClauseSyntax) If Not SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type) Then ReportError(RudeEditKind.TypeUpdate, newNode.Parent, newNode.Parent) @@ -2977,21 +2879,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ClassifyUpdate(oldNode.Identifier, newNode.Identifier) End Sub - Private Shared Function AreModifiersEquivalent(oldModifiers As SyntaxTokenList, newModifiers As SyntaxTokenList, ignore As SyntaxKind) As Boolean - Dim oldIgnoredModifierIndex = oldModifiers.IndexOf(ignore) - Dim newIgnoredModifierIndex = newModifiers.IndexOf(ignore) - - If oldIgnoredModifierIndex >= 0 Then - oldModifiers = oldModifiers.RemoveAt(oldIgnoredModifierIndex) - End If - - If newIgnoredModifierIndex >= 0 Then - newModifiers = newModifiers.RemoveAt(newIgnoredModifierIndex) - End If - - Return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers) - End Function - Private Sub ClassifyMethodBodyRudeUpdate(oldBody As MethodBlockBaseSyntax, newBody As MethodBlockBaseSyntax, containingMethod As MethodBlockSyntax, diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/Extensions.vb b/src/Features/VisualBasic/Portable/ExtractMethod/Extensions.vb index 5bacc240252ff..bfbbec93ed1b4 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/Extensions.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/Extensions.vb @@ -360,6 +360,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod If tuple.Item2.Kind = SyntaxKind.None Then Exit For End If + list.Add(tuple.Item2) Next diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb index 7a92589b69c61..92102b94dc4a9 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb @@ -363,6 +363,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod SyntaxFactory.Token(SyntaxKind.FalseKeyword)))))) End If End If + Return SyntaxFactory.AwaitExpression(invocation) End If diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb index 9f271f38908ef..dedff2bc9d048 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicSelectionValidator.vb @@ -201,6 +201,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod If range Is Nothing Then Return True End If + result = semanticModel.AnalyzeDataFlow(range.Item1, range.Item2) End If @@ -253,6 +254,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = firstToken .LastTokenInFinalSpan = lastToken End With + Return clone End Function @@ -284,6 +286,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then With clone .Status = .Status.With(OperationStatusFlag.None, VBFeaturesResources.next_statement_control_variable_doesn_t_have_matching_declaration_statement) End With + Return clone End If @@ -294,6 +297,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then With clone .Status = .Status.With(OperationStatusFlag.None, VBFeaturesResources.next_statement_control_variable_doesn_t_have_matching_declaration_statement) End With + Return clone End If @@ -304,6 +308,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = firstStatement.GetFirstToken(includeZeroWidth:=True) .LastTokenInFinalSpan = nextStatement.GetLastToken(includeZeroWidth:=True) End With + Return clone End Function @@ -339,6 +344,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = Nothing .LastTokenInFinalSpan = Nothing End With + Return clone End If @@ -348,6 +354,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = firstValidNode.GetFirstToken(includeZeroWidth:=True) .LastTokenInFinalSpan = firstValidNode.GetLastToken(includeZeroWidth:=True) End With + Return clone End Function @@ -369,6 +376,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = outerNode.GetFirstToken(includeZeroWidth:=True) .LastTokenInFinalSpan = outerNode.GetLastToken(includeZeroWidth:=True) End With + Return clone End If @@ -380,6 +388,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then With clone .Status = clone.Status.With(OperationStatusFlag.None, VBFeaturesResources.no_valid_statement_range_to_extract_out) End With + Return clone End If @@ -395,6 +404,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = expression.GetFirstToken(includeZeroWidth:=True) .LastTokenInFinalSpan = expression.GetLastToken(includeZeroWidth:=True) End With + Return clone End If @@ -407,6 +417,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then With clone .Status = clone.Status.With(OperationStatusFlag.None, VBFeaturesResources.no_valid_statement_range_to_extract_out) End With + Return clone End If @@ -415,6 +426,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = singleStatement.GetFirstToken(includeZeroWidth:=True) .LastTokenInFinalSpan = singleStatement.GetLastToken(includeZeroWidth:=True) End With + Return clone End If @@ -438,6 +450,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = parent.GetFirstToken() .LastTokenInFinalSpan = parent.GetLastToken() End With + Return clone End If End If @@ -446,6 +459,7 @@ result.ReadOutside().Any(Function(s) Equals(s, local)) Then .FirstTokenInFinalSpan = statement1.GetFirstToken(includeZeroWidth:=True) .LastTokenInFinalSpan = statement2.GetLastToken(includeZeroWidth:=True) End With + Return clone End Function diff --git a/src/Features/VisualBasic/Portable/FullyQualify/VisualBasicFullyQualifyCodeFixProvider.vb b/src/Features/VisualBasic/Portable/FullyQualify/VisualBasicFullyQualifyCodeFixProvider.vb index 20872e43ef416..5d3b2831c7dfd 100644 --- a/src/Features/VisualBasic/Portable/FullyQualify/VisualBasicFullyQualifyCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/FullyQualify/VisualBasicFullyQualifyCodeFixProvider.vb @@ -91,8 +91,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.FullyQualify If simpleName IsNot Nothing Then Return simpleName End If + qn = TryCast(left, QualifiedNameSyntax) End While + Return Nothing End Function diff --git a/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb b/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb index 7796e3e8b1f20..0aef8dafbcb20 100644 --- a/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb +++ b/src/Features/VisualBasic/Portable/GenerateConstructor/VisualBasicGenerateConstructorService.vb @@ -176,12 +176,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateConstructor If (constructorStatements.IsEmpty()) Then Return Nothing End If + Dim constructorInitializerSyntax = constructorStatements(0) Dim expressionStatement = TryCast(constructorInitializerSyntax, ExpressionStatementSyntax) If (expressionStatement IsNot Nothing AndAlso expressionStatement.Expression.IsKind(SyntaxKind.InvocationExpression)) Then Dim methodSymbol = TryCast(semanticModel.GetSymbolInfo(expressionStatement.Expression, cancellationToken).Symbol, IMethodSymbol) Return If(methodSymbol IsNot Nothing AndAlso methodSymbol.MethodKind = MethodKind.Constructor, methodSymbol, Nothing) End If + Return Nothing End Function End Class diff --git a/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateConversionService.vb b/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateConversionService.vb index 1622ceb144d00..c11b85604a33b 100644 --- a/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateConversionService.vb +++ b/src/Features/VisualBasic/Portable/GenerateMember/GenerateParameterizedMember/VisualBasicGenerateConversionService.vb @@ -122,6 +122,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateMember.GenerateMethod If Not ValidateTypeToGenerateIn(typeToGenerateIn, True, classInterfaceModuleStructTypes) Then typeToGenerateIn = parameterSymbol End If + Return True End Function @@ -137,6 +138,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateMember.GenerateMethod If Not ValidateTypeToGenerateIn(typeToGenerateIn, True, classInterfaceModuleStructTypes) Then typeToGenerateIn = parameterSymbol End If + Return True End Function @@ -144,6 +146,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateMember.GenerateMethod If typeToGenerateIn.IsGenericType Then typeToGenerateIn = typeToGenerateIn.ConstructUnboundGenericType.ConstructedFrom End If + Return CodeGenerationSymbolFactory.CreateMethodSymbol( attributes:=ImmutableArray(Of AttributeData).Empty, accessibility:=Nothing, diff --git a/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb b/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb index a803ca6f207f6..df35bfcb8462b 100644 --- a/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb +++ b/src/Features/VisualBasic/Portable/GenerateType/VisualBasicGenerateTypeService.vb @@ -686,6 +686,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateType If fieldInitializer IsNot Nothing Then Return typeInference.InferType(semanticModel, fieldInitializer.Name, True, cancellationToken) End If + Return Nothing End Function diff --git a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb index faac8f5e1eb83..00a9e55be5864 100644 --- a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb +++ b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb @@ -51,10 +51,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InvertIf If falseStatementOpt.Count > 0 Then updatedIf = updatedIf.WithElseBlock(SyntaxFactory.ElseBlock(falseStatementOpt)) + ElseIf HasComment(ifNode.ElseBlock) Then + ' If the original else block has leading trivia + ' then we need to mantain that trivia for the new statement. + ' This should be attached to the "EndIf" statement now + ' because that will make it show up in the else block + Dim newEndIfStatement = SyntaxFactory.EndIfStatement().WithLeadingTrivia(ifNode.ElseBlock.GetLeadingTrivia()) + updatedIf = updatedIf.WithEndIfStatement(newEndIfStatement) + updatedIf = updatedIf.WithElseBlock(SyntaxFactory.ElseBlock()) + Else + updatedIf = updatedIf.WithElseBlock(Nothing) End If Return updatedIf End Function + + Private Shared Function HasComment(elseBlock As ElseBlockSyntax) As Boolean + Return elseBlock.GetLeadingTrivia().Any(Function(trivia) trivia.IsKind(SyntaxKind.CommentTrivia)) + End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicAnonymousTypeDisplayService.vb b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicAnonymousTypeDisplayService.vb index 6f8c8515459e5..84a3e998cbcb9 100644 --- a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicAnonymousTypeDisplayService.vb +++ b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicAnonymousTypeDisplayService.vb @@ -97,6 +97,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices If Not first Then members.Add(Punctuation(SyntaxFacts.GetText(SyntaxKind.CommaToken))) End If + first = False If [property].IsReadOnly Then diff --git a/src/Features/VisualBasic/Portable/RemoveSharedFromModuleMembers/VisualBasicRemoveSharedFromModuleMembersCodeFixProvider.vb b/src/Features/VisualBasic/Portable/RemoveSharedFromModuleMembers/VisualBasicRemoveSharedFromModuleMembersCodeFixProvider.vb index 48904e69fba69..37cf39950bde1 100644 --- a/src/Features/VisualBasic/Portable/RemoveSharedFromModuleMembers/VisualBasicRemoveSharedFromModuleMembersCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/RemoveSharedFromModuleMembers/VisualBasicRemoveSharedFromModuleMembersCodeFixProvider.vb @@ -64,6 +64,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveSharedFromModuleMembers Dim newNode = GetReplacement(document, node) editor.ReplaceNode(node, newNode) Next + Return Task.CompletedTask End Function diff --git a/src/Features/VisualBasic/Portable/SignatureHelp/GenericNameSignatureHelpProvider.vb b/src/Features/VisualBasic/Portable/SignatureHelp/GenericNameSignatureHelpProvider.vb index f68918a5d06cc..a25125469b11f 100644 --- a/src/Features/VisualBasic/Portable/SignatureHelp/GenericNameSignatureHelpProvider.vb +++ b/src/Features/VisualBasic/Portable/SignatureHelp/GenericNameSignatureHelpProvider.vb @@ -198,6 +198,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SignatureHelp parts.Add(Punctuation(SyntaxKind.CommaToken)) parts.Add(Space()) End If + parts.Add(Keyword(SyntaxKind.NewKeyword)) End If diff --git a/src/Features/VisualBasic/Portable/SpellCheck/VisualBasicSpellCheckCodeFixProvider.vb b/src/Features/VisualBasic/Portable/SpellCheck/VisualBasicSpellCheckCodeFixProvider.vb index 580082e865f5c..5dbd1d98bac50 100644 --- a/src/Features/VisualBasic/Portable/SpellCheck/VisualBasicSpellCheckCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/SpellCheck/VisualBasicSpellCheckCodeFixProvider.vb @@ -76,6 +76,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SpellCheck If nameToken.IsBracketed() AndAlso Not newName.StartsWith("[") Then newName = "[" + newName + "]" End If + Return SyntaxFactory.Identifier(newName).WithTriviaFrom(nameToken) End Function End Class diff --git a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj index 82bb66a741dc3..8988a5b254517 100644 --- a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj +++ b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj @@ -258,6 +258,7 @@ <_OptimizedNuGetPackageVersionSuffix Condition="'$(OfficialBuild)' != 'true'">vs-ci <_OptimizedNuGetPackageVersionSuffix Condition="'$(OfficialBuild)' == 'true'">vs-$(VersionSuffixDateStamp)-$(VersionSuffixBuildOfTheDayPadded) + <_OptimizedNuGetPackageVersionSuffix Condition="'$(PreReleaseVersionLabel)' == 'pr-validation'">pr-validation-$(VersionSuffixDateStamp)-$(VersionSuffixBuildOfTheDayPadded) diff --git a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs index 6eba6f1291013..9dc2949a346a5 100644 --- a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs +++ b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SQLite.v2; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Test.Utilities; @@ -68,7 +69,9 @@ public void GlobalSetup() .WithChangedOption(StorageOptions.DatabaseMustSucceed, true))); var connectionPoolService = _workspace.ExportProvider.GetExportedValue(); - _storageService = new SQLitePersistentStorageService(_workspace.Options, connectionPoolService, new LocationService()); + _storageService = new SQLitePersistentStorageService( + _workspace.Options, connectionPoolService, new LocationService(), + _workspace.ExportProvider.GetExportedValue().GetListener(FeatureAttribute.PersistentStorage)); var solution = _workspace.CurrentSolution; _storage = _storageService.GetStorageWorkerAsync(_workspace, SolutionKey.ToSolutionKey(solution), solution, CancellationToken.None).AsTask().GetAwaiter().GetResult(); diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index 0f4ab0b7f04fc..9199149dd86ac 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -640,4 +640,7 @@ Automatically complete statement on semicolon + + Prefer 'null' check over type check + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs index b971a269abbb5..30b7dd781d14a 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs @@ -438,6 +438,12 @@ private static bool NodeIsSupported(bool test, SyntaxNode node) /// of the field. /// If true, only members supported by Code Model are returned. public override IEnumerable GetMemberNodes(SyntaxNode container, bool includeSelf, bool recursive, bool logicalFields, bool onlySupportedNodes) + { + // Filter out all records from code model, they are not supported at all. + return GetMemberNodesWorker(container, includeSelf, recursive, logicalFields, onlySupportedNodes).Where(t => t is not RecordDeclarationSyntax); + } + + private IEnumerable GetMemberNodesWorker(SyntaxNode container, bool includeSelf, bool recursive, bool logicalFields, bool onlySupportedNodes) { if (!IsContainerNode(container)) { diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs index 93823b5ae6970..45b98a5af483a 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -26,4 +26,4 @@ public CSharpCodeCleanUpFixerProvider( { } } -} \ No newline at end of file +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index eb94fe1fa39dc..da1a53ea12d4c 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -33,7 +33,9 @@ + Unchecked="Enable_pull_diagnostics_experimental_requires_restart_Unchecked" + Indeterminate="Enable_pull_diagnostics_experimental_requires_restart_Indeterminate" + IsThreeState="True"/> + + { // If the option has not been set by the user, check if the option to remove unused references @@ -129,7 +130,10 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(ShowHintsForLambdaParameterTypes, InlineHintsOptions.ForLambdaParameterTypes, LanguageNames.CSharp); BindToOption(ShowHintsForImplicitObjectCreation, InlineHintsOptions.ForImplicitObjectCreation, LanguageNames.CSharp); - BindToOption(ShowInheritanceMargin, FeatureOnOffOptions.ShowInheritanceMargin, LanguageNames.CSharp); + // If the option has not been set by the user, check if the option is enabled from experimentation. + // If so, default to that. Otherwise default to disabled + BindToOption(ShowInheritanceMargin, FeatureOnOffOptions.ShowInheritanceMargin, LanguageNames.CSharp, () => + experimentationService?.IsExperimentEnabled(WellKnownExperimentNames.InheritanceMargin) ?? false); } // Since this dialog is constructed once for the lifetime of the application and VS Theme can be changed after the application has started, @@ -151,8 +155,21 @@ internal override void OnLoad() private void UpdatePullDiagnosticsOptions() { - Enable_pull_diagnostics_experimental_requires_restart.IsChecked = OptionStore.GetOption(InternalDiagnosticsOptions.NormalDiagnosticMode) == DiagnosticMode.Pull; + var normalPullDiagnosticsOption = OptionStore.GetOption(InternalDiagnosticsOptions.NormalDiagnosticMode); + Enable_pull_diagnostics_experimental_requires_restart.IsChecked = GetDiagnosticModeCheckboxValue(normalPullDiagnosticsOption); + Enable_Razor_pull_diagnostics_experimental_requires_restart.IsChecked = OptionStore.GetOption(InternalDiagnosticsOptions.RazorDiagnosticMode) == DiagnosticMode.Pull; + + static bool? GetDiagnosticModeCheckboxValue(DiagnosticMode mode) + { + return mode switch + { + DiagnosticMode.Push => false, + DiagnosticMode.Pull => true, + DiagnosticMode.Default => null, + _ => throw new System.ArgumentException("unknown diagnostic mode"), + }; + } } private void Enable_pull_diagnostics_experimental_requires_restart_Checked(object sender, RoutedEventArgs e) @@ -167,6 +184,12 @@ private void Enable_pull_diagnostics_experimental_requires_restart_Unchecked(obj UpdatePullDiagnosticsOptions(); } + private void Enable_pull_diagnostics_experimental_requires_restart_Indeterminate(object sender, RoutedEventArgs e) + { + this.OptionStore.SetOption(InternalDiagnosticsOptions.NormalDiagnosticMode, DiagnosticMode.Default); + UpdatePullDiagnosticsOptions(); + } + private void Enable_Razor_pull_diagnostics_experimental_requires_restart_Checked(object sender, RoutedEventArgs e) { this.OptionStore.SetOption(InternalDiagnosticsOptions.RazorDiagnosticMode, DiagnosticMode.Pull); diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs index 8454fcf481045..9ddfff6204ba3 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs @@ -273,6 +273,9 @@ public static string Enable_all_features_in_opened_files_from_source_generators_ public static string Option_Enable_file_logging_for_diagnostics => ServicesVSResources.Enable_file_logging_for_diagnostics; + public static string Option_Skip_analyzers_for_implicitly_triggered_builds + => ServicesVSResources.Skip_analyzers_for_implicitly_triggered_builds; + public static string Show_inheritance_margin => ServicesVSResources.Show_inheritance_margin; diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs index efa1016d1bb16..e05ccaa61e125 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs @@ -85,5 +85,11 @@ public int AutomaticallyCompleteStatementOnSemicolon get { return GetBooleanOption(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon); } set { SetBooleanOption(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon, value); } } + + public int SkipAnalyzersForImplicitlyTriggeredBuilds + { + get { return GetBooleanOption(FeatureOnOffOptions.SkipAnalyzersForImplicitlyTriggeredBuilds); } + set { SetBooleanOption(FeatureOnOffOptions.SkipAnalyzersForImplicitlyTriggeredBuilds, value); } + } } } diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs index cb3fd043aa11c..def7d40b58687 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs @@ -237,6 +237,12 @@ public string Style_PreferIsNullCheckOverReferenceEqualityMethod set { SetXmlOption(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, value); } } + public string Style_PreferNullCheckOverTypeCheck + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferNullCheckOverTypeCheck); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferNullCheckOverTypeCheck, value); } + } + public string Style_PreferConditionalExpressionOverAssignment { get { return GetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverAssignment); } diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index 0ff0e414add14..41467fe1ed25d 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -1058,6 +1058,36 @@ void M2(string value1, string value2) //] }} }} +"; + + private static readonly string s_preferNullcheckOverTypeCheck = $@" +using System; + +class Customer +{{ + void M1(string value1, string value2) + {{ +//[ + // {ServicesVSResources.Prefer_colon} + if (value1 is null) + return; + + if (value2 is not null) + return; +//] + }} + void M2(string value1, string value2) + {{ +//[ + // {ServicesVSResources.Over_colon} + if (value1 is not object) + return; + + if (value2 is object) + return; +//] + }} +}} "; #region expression and block bodies @@ -2011,6 +2041,7 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferCoalesceExpression, ServicesVSResources.Prefer_coalesce_expression, s_preferCoalesceExpression, s_preferCoalesceExpression, this, optionStore, nullCheckingGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, this, optionStore, nullCheckingGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, CSharpVSResources.Prefer_is_null_for_reference_equality_checks, s_preferIsNullOverReferenceEquals, s_preferIsNullOverReferenceEquals, this, optionStore, nullCheckingGroupTitle)); + CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferNullCheckOverTypeCheck, CSharpVSResources.Prefer_null_check_over_type_check, s_preferNullcheckOverTypeCheck, s_preferNullcheckOverTypeCheck, this, optionStore, nullCheckingGroupTitle)); // Using directive preferences. CodeStyleItems.Add(new EnumCodeStyleOptionViewModel( diff --git a/src/VisualStudio/CSharp/Impl/VSPackage.resx b/src/VisualStudio/CSharp/Impl/VSPackage.resx index 2177228dbf5fb..dd11388e0fa0c 100644 --- a/src/VisualStudio/CSharp/Impl/VSPackage.resx +++ b/src/VisualStudio/CSharp/Impl/VSPackage.resx @@ -190,7 +190,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; C# Advanced options page keywords diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf index 0040a6a73293d..d6c965cc620ff 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf @@ -122,6 +122,11 @@ U kontrol rovnosti odkazů dávat přednost možnosti is null 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Upřednostňovat porovnávání vzorů diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf index 09219c0444092..c9444c135d8eb 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf @@ -122,6 +122,11 @@ "is null" für Verweisübereinstimmungsprüfungen vorziehen 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Musterabgleich bevorzugen diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf index 26e4894cb76cd..60b4ae0cd42f8 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf @@ -122,6 +122,11 @@ Preferir “is null” para comprobaciones de igualdad de referencias 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferir coincidencia de patrones diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf index 5b98d2c024953..46d4e596b2dd4 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf @@ -122,6 +122,11 @@ Préférer 'is nul' pour les vérifications d'égalité de référence 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Préférer les critères spéciaux diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf index f7c2cad124934..11b64a1b14483 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf @@ -122,6 +122,11 @@ Preferisci 'is null' per i controlli di uguaglianza dei riferimenti 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferisci i criteri di ricerca diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf index f3ef968687913..cecf7812a142f 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf @@ -122,6 +122,11 @@ 参照の等値性のチェックには 'is null' を優先する 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching パターン マッチングを優先する diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf index e87fdf18182d5..4a568dd1e004b 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf @@ -122,6 +122,11 @@ 참조 같음 검사에 대해 'is null' 선호 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching 패턴 일치 선호 diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf index 0899eae4a2035..079f151946fd7 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf @@ -122,6 +122,11 @@ Preferuj wyrażenie „is null” w przypadku sprawdzeń odwołań pod kątem równości 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferuj dopasowywanie do wzorca diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf index 43527d65a3b0d..afb037c984b51 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf @@ -122,6 +122,11 @@ Preferir 'is null' para as verificações de igualdade de referência 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Preferir a correspondência de padrões diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf index a32c43edda66e..5b284d87e3beb 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf @@ -122,6 +122,11 @@ Использовать "is null" вместо проверки ссылок на равенство. 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Предпочитать соответствие шаблону diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf index 65cd8caa4d2b8..f36be771b8a70 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf @@ -122,6 +122,11 @@ Başvuru eşitliği denetimleri için 'is null'ı tercih et 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching Desen eşleştirmeyi tercih et diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf index 6694126dc85fa..7d200d4f2e050 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf @@ -122,6 +122,11 @@ 引用相等检查偏好 “is null” 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching 首选模式匹配 diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf index c23c57609349a..656e68e1a6e94 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf @@ -122,6 +122,11 @@ 參考相等檢查最好使用 'is null' 'is null' is a C# string and should not be localized. + + Prefer 'null' check over type check + Prefer 'null' check over type check + + Prefer pattern matching 建議使用模式比對 diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf index 5ceecaa1ec191..e6e55db2caa55 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Zobrazovat vložené nápovědy; Zobrazit diagnostiku pro zavřené soubory; Vybarvit regulární výraz; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf index ec058fb7caf1b..b5ad74ac5834c 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Inlinehinweise anzeigen; Diagnoseinformationen für geschlossene Dateien anzeigen; Reguläre Ausdrücke farbig hervorheben; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf index 79041ce406cea..68b0b945a7305 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Mostrar sugerencias insertadas; Mostrar diagnóstico para archivos cerrados; Colorear expresión regular; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf index 60533c2835efe..5336e5ec7aa05 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Afficher les indicateurs inline ; Afficher les diagnostics pour les fichiers fermés ; Mettre en couleurs l'expression régulière ; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf index 18600b271474a..6991c05f5ff10 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Visualizza suggerimenti inline; Mostra diagnostica per file chiusi; Colora espressione regolare; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf index d80ccb3217566..04f90e6bf6371 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; インラインのヒントを表示する; 閉じているファイルの診断結果を表示する; 正規表現をカラー化する; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf index e08d82d4fbf68..b198ec7fe5b06 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; 인라인 힌트 표시; 닫힌 파일에 대한 진단 표시; 정규식 색 지정, diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf index 1e0ac9a636259..d6435ffa1e693 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Wyświetlaj wskazówki w tekście; Pokaż dane diagnostyczne dla zamkniętych plików; Koloruj wyrażenia regularne; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf index 8193368f22710..4a258a0fba38c 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Exibir as dicas embutidas; Mostrar os diagnósticos de arquivos fechados; Colorir a expressão regular; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf index 8db36cfa8dba9..c6707c64bbd54 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Отображать встроенные подсказки; Отображать диагностику для закрытых файлов; Выделять регулярные выражения цветом; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf index 20dda4691f5cf..c46a7f5a8da08 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; Satır içi ipuçlarını göster; Kapatılan dosyalara ilişkin tanılamaları göster; Normal ifadeyi renklendir; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf index 7192bb466f6d9..0356f99c0c477 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; 显示内联提示; 显示对已关闭文件的诊断; 对正则表达式着色; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf index 44ee0c2b86226..33daee66f28c8 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf @@ -81,7 +81,8 @@ prefer auto properties; regex; regular expression; Use enhanced colors; -Editor Color Scheme; +Editor Color Scheme; +Inheritance Margin; 顯示內嵌提示; 顯示已關閉檔案的診斷; 為規則運算式著色; diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs index 39e463281d2cc..0b6a6abbf4ba7 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs @@ -122,6 +122,7 @@ private async Task AssertResetInteractiveAsync( { expectedSubmissions.AddRange(expectedReferences.Select(r => r + newLineCharacter)); } + if (expectedUsings.Any()) { expectedSubmissions.Add(string.Join(newLineCharacter, expectedUsings) + newLineCharacter); diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index 09ffc4b65775b..d760162eb2f23 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.LanguageServices.UnitTests; @@ -841,6 +842,7 @@ private void DoSimultaneousReads(Func> read, string expectedValue) exceptions.Add(ex); } } + countdown.Signal(); }); } @@ -873,6 +875,7 @@ private void DoSimultaneousWrites(Func write) exceptions.Add(ex); } } + countdown.Signal(); }, i); } diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index 06976afc94576..5680a104422e0 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SQLite.v2; using Microsoft.CodeAnalysis.Storage; using Xunit; @@ -23,7 +24,12 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices public class SQLiteV2PersistentStorageTests : AbstractPersistentStorageTests { internal override AbstractPersistentStorageService GetStorageService(OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string relativePathBase) - => new SQLitePersistentStorageService(options, exportProvider.GetExports().Single().Value, locationService, faultInjector); + => new SQLitePersistentStorageService( + options, + exportProvider.GetExports().Single().Value, + locationService, + exportProvider.GetExports().Single().Value.GetListener(FeatureAttribute.PersistentStorage), + faultInjector); [Fact] public async Task TestCrashInNewConnection() diff --git a/src/VisualStudio/Core/Def/Commands.vsct b/src/VisualStudio/Core/Def/Commands.vsct index 422b93c7d0bfd..40fdb4225579d 100644 --- a/src/VisualStudio/Core/Def/Commands.vsct +++ b/src/VisualStudio/Core/Def/Commands.vsct @@ -411,6 +411,19 @@ RemoveUnusedReferences + + @@ -516,6 +529,10 @@ + + + + @@ -601,6 +618,8 @@ + + diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs index ed941cfef95a7..0bb2230665580 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs @@ -32,6 +32,7 @@ public void UpdateText(IReadOnlyList changes) { return; } + TextEditApplication.UpdateText(changes.ToImmutableArray(), buffer, EditOptions.DefaultMinimalChange); } } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/View/ColumnDefnitions/FormattingValueColumnDefinition.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/View/ColumnDefnitions/FormattingValueColumnDefinition.cs index 48128122cc5b2..ccaed8345d11f 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/View/ColumnDefnitions/FormattingValueColumnDefinition.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/View/ColumnDefnitions/FormattingValueColumnDefinition.cs @@ -44,6 +44,7 @@ public override bool TryCreateColumnContent(ITableEntryHandle entry, bool single content = null; return false; } + if (setting.Type == typeof(bool)) { content = new FormattingBoolSettingView(setting); diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs index ebb94d37e7329..89268b2a5dd0f 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs @@ -122,6 +122,7 @@ ISettingsEditorView GetFormattingView() { throw new InvalidOperationException("Unable to get formatter settings"); } + var viewModel = new FormattingViewModel(dataProvider, _controlProvider, _tableMangerProvider); return new FormattingSettingsView(viewModel); } @@ -133,6 +134,7 @@ ISettingsEditorView GetCodeStyleView() { throw new InvalidOperationException("Unable to get code style settings"); } + var viewModel = new CodeStyleSettingsViewModel(dataProvider, _controlProvider, _tableMangerProvider); return new CodeStyleSettingsView(viewModel); } @@ -240,6 +242,7 @@ public int FDoIdle(uint grfidlef) { _control.SynchronizeSettings(); } + return S_OK; } diff --git a/src/VisualStudio/Core/Def/Guids.cs b/src/VisualStudio/Core/Def/Guids.cs index 6f058e3c50002..994acb0fbbebc 100644 --- a/src/VisualStudio/Core/Def/Guids.cs +++ b/src/VisualStudio/Core/Def/Guids.cs @@ -125,6 +125,9 @@ internal static class Guids public static readonly Guid RoslynCommandSetId = new(RoslynCommandSetIdString); public static readonly Guid RoslynGroupId = new(RoslynGroupIdString); + public const string ValueTrackingToolWindowIdString = "60a19d42-2dd7-43f3-be90-c7a9cb7d28f4"; + public static readonly Guid ValueTrackingToolWindowId = new(ValueTrackingToolWindowIdString); + // TODO: Remove pending https://github.com/dotnet/roslyn/issues/8927 . // Interactive guids public const string InteractiveCommandSetIdString = "00B8868B-F9F5-4970-A048-410B05508506"; diff --git a/src/VisualStudio/Core/Def/ID.RoslynCommands.cs b/src/VisualStudio/Core/Def/ID.RoslynCommands.cs index d89285327286e..ad65fd8e87b1f 100644 --- a/src/VisualStudio/Core/Def/ID.RoslynCommands.cs +++ b/src/VisualStudio/Core/Def/ID.RoslynCommands.cs @@ -50,6 +50,7 @@ public static class RoslynCommands public const int RunCodeAnalysisForProject = 0x0201; public const int RemoveUnusedReferences = 0x0202; + public const int GoToValueTrackingWindow = 0x0203; } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialogViewModel.ParameterViewModels.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialogViewModel.ParameterViewModels.cs index 4f5be59c9102f..ac655f93c7d32 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialogViewModel.ParameterViewModels.cs +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialogViewModel.ParameterViewModels.cs @@ -233,6 +233,7 @@ public override string Default case LanguageNames.VisualBasic: return NullText("Nothing", "Nothing"); } + return string.Empty; string NullText(string @null, string @default) diff --git a/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs b/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs index 27a6efccbd0ea..b2992efa119ab 100644 --- a/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs +++ b/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Editor.Commanding.Commands; using Microsoft.VisualStudio.Editor.Commanding; using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.LanguageServices.ValueTracking; namespace Microsoft.VisualStudio.Editor.Implementation { @@ -28,5 +29,9 @@ internal sealed class CommandBindings [Export] [CommandBinding(Guids.CSharpGroupIdString, ID.CSharpCommands.ContextOrganizeRemoveAndSort, typeof(SortAndRemoveUnnecessaryImportsCommandArgs))] internal CommandBindingDefinition contextOrganizeRemoveAndSortCommandBinding; + + [Export] + [CommandBinding(Guids.RoslynGroupIdString, ID.RoslynCommands.GoToValueTrackingWindow, typeof(ValueTrackingEditorCommandArgs))] + internal CommandBindingDefinition gotoDataFlowToolCommandBinding; } } diff --git a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs index 05015c4ebea93..52df9e1854c5e 100644 --- a/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/Implementation/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.Designer.Interfaces; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; @@ -66,6 +67,7 @@ internal class VisualStudioDesignerAttributeService public VisualStudioDesignerAttributeService( VisualStudioWorkspaceImpl workspace, IThreadingContext threadingContext, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, Shell.SVsServiceProvider serviceProvider) : base(threadingContext) { @@ -75,6 +77,7 @@ public VisualStudioDesignerAttributeService( _workQueue = new AsyncBatchingWorkQueue( TimeSpan.FromSeconds(1), this.NotifyProjectSystemAsync, + asynchronousOperationListenerProvider.GetListener(FeatureAttribute.DesignerAttributes), ThreadingContext.DisposalToken); } @@ -143,7 +146,7 @@ public void StartScanningForDesignerAttributesInCurrentProcess(CancellationToken workspaceKinds: WorkspaceKind.Host)); } - private async Task NotifyProjectSystemAsync( + private async ValueTask NotifyProjectSystemAsync( ImmutableArray data, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs index 663e1bc86c070..10f0351999e98 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs @@ -186,9 +186,15 @@ public async Task HasChangesAsync(string? sourceFilePath, CancellationToke { try { + var debuggingSession = _debuggingSession; + if (debuggingSession == null) + { + return false; + } + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); - return await GetDebuggingSession().HasChangesAsync(solution, activeStatementSpanProvider, sourceFilePath, cancellationToken).ConfigureAwait(false); + return await debuggingSession.HasChangesAsync(solution, activeStatementSpanProvider, sourceFilePath, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs index 06e156a4c3b0c..e9a5c2fa869f1 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; @@ -78,6 +79,9 @@ private RemoteDebuggingSessionProxy GetDebuggingSession() return debuggingSession; } + private Solution GetCurrentCompileTimeSolution() + => _proxy.Workspace.Services.GetRequiredService().GetCompileTimeSolution(_proxy.Workspace.CurrentSolution); + public async ValueTask StartSessionAsync(CancellationToken cancellationToken) { if (_disabled) @@ -87,7 +91,7 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); _debuggingSession = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, reportDiagnostics: true, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) @@ -107,7 +111,7 @@ public async ValueTask GetUpdatesAsync(CancellationToke try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var (moduleUpdates, diagnosticData, rudeEdits) = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, s_solutionActiveStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); var updates = moduleUpdates.Updates.SelectAsArray( diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VisualStudioWorkspaceEditAndContinueListener.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VisualStudioWorkspaceEditAndContinueListener.cs new file mode 100644 index 0000000000000..bd737a536714d --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VisualStudioWorkspaceEditAndContinueListener.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.EditAndContinue +{ + /// + /// Connects to the ServiceHub services. + /// Launches ServiceHub if it is not running yet and starts services that push information from to the ServiceHub process. + /// + [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared] + internal sealed class VisualStudioWorkspaceEditAndContinueListener : IEventListener, IEventListenerStoppable + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public VisualStudioWorkspaceEditAndContinueListener() + { + } + + public void StartListening(Workspace workspace, object serviceOpt) + { + if (workspace is not VisualStudioWorkspace) + { + return; + } + + workspace.DocumentOpened += WorkspaceDocumentOpened; + } + + public void StopListening(Workspace workspace) + { + if (workspace is not VisualStudioWorkspace) + { + return; + } + + workspace.DocumentOpened -= WorkspaceDocumentOpened; + } + + private void WorkspaceDocumentOpened(object? sender, DocumentEventArgs e) + { + var proxy = new RemoteEditAndContinueServiceProxy(e.Document.Project.Solution.Workspace); + _ = Task.Run(() => proxy.OnSourceFileUpdatedAsync(e.Document, CancellationToken.None)).ReportNonFatalErrorAsync(); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index 1ec25628cadec..c2f51fd3e1b48 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -143,6 +143,7 @@ protected AbstractTableDataSourceFindUsagesContext( _progressQueue = new AsyncBatchingWorkQueue<(int current, int maximum)>( TimeSpan.FromMilliseconds(250), this.UpdateTableProgressAsync, + presenter._asyncListener, CancellationTokenSource.Token); } @@ -406,7 +407,7 @@ protected sealed override ValueTask ReportProgressAsync(int current, int maximum return default; } - private Task UpdateTableProgressAsync(ImmutableArray<(int current, int maximum)> nextBatch, CancellationToken _) + private ValueTask UpdateTableProgressAsync(ImmutableArray<(int current, int maximum)> nextBatch, CancellationToken _) { if (!nextBatch.IsEmpty) { @@ -426,7 +427,7 @@ private Task UpdateTableProgressAsync(ImmutableArray<(int current, int maximum)> _findReferencesWindow.SetProgress(current, maximum); } - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; } #endregion diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs index 63db39ae31c06..bab65db029281 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServices.Implementation.FindReferences; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; @@ -38,6 +39,7 @@ internal partial class StreamingFindUsagesPresenter : public readonly ClassificationTypeMap TypeMap; public readonly IEditorFormatMapService FormatMapService; + private readonly IAsynchronousOperationListener _asyncListener; public readonly IClassificationFormatMap ClassificationFormatMap; private readonly Workspace _workspace; @@ -55,6 +57,7 @@ public StreamingFindUsagesPresenter( ClassificationTypeMap typeMap, IEditorFormatMapService formatMapService, IClassificationFormatMapService classificationFormatMapService, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, [ImportMany] IEnumerable> columns) : this(workspace, threadingContext, @@ -62,7 +65,8 @@ public StreamingFindUsagesPresenter( typeMap, formatMapService, classificationFormatMapService, - GetCustomColumns(columns)) + GetCustomColumns(columns), + asynchronousOperationListenerProvider) { } @@ -77,7 +81,8 @@ public StreamingFindUsagesPresenter( exportProvider.GetExportedValue(), exportProvider.GetExportedValue(), exportProvider.GetExportedValue(), - exportProvider.GetExportedValues()) + exportProvider.GetExportedValues(), + exportProvider.GetExportedValue()) { } @@ -89,13 +94,15 @@ private StreamingFindUsagesPresenter( ClassificationTypeMap typeMap, IEditorFormatMapService formatMapService, IClassificationFormatMapService classificationFormatMapService, - IEnumerable columns) + IEnumerable columns, + IAsynchronousOperationListenerProvider asyncListenerProvider) : base(threadingContext, assertIsForeground: false) { _workspace = workspace; _serviceProvider = serviceProvider; TypeMap = typeMap; FormatMapService = formatMapService; + _asyncListener = asyncListenerProvider.GetListener(FeatureAttribute.FindReferences); ClassificationFormatMap = classificationFormatMapService.GetClassificationFormatMap("tooltip"); _customColumns = columns.ToImmutableArray(); diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs index f1184deabce3d..9ee34cd9affe9 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs @@ -45,9 +45,6 @@ public InheritanceGlyphFactory( var membersOnLine = inheritanceMarginTag.MembersOnLine; Contract.ThrowIfTrue(membersOnLine.IsEmpty); - // ZoomLevel of textView is percentage based. (e.g. 20 -> 400 means 20% -> 400%) - // and the scaleFactor of CrispImage is 1 based. (e.g 1 means 100%) - var scaleFactor = _textView.ZoomLevel / 100; return new MarginGlyph.InheritanceMargin( _threadingContext, _streamingFindUsagesPresenter, @@ -55,7 +52,7 @@ public InheritanceGlyphFactory( _classificationFormatMap, _operationExecutor, inheritanceMarginTag, - scaleFactor); + _textView); } return null; diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs index 34a15b3a44a30..d9a665b4283c7 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs @@ -16,100 +16,78 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMarg { internal static class InheritanceMarginHelpers { + private static readonly ImmutableArray s_relationships_Shown_As_I_Up_Arrow + = ImmutableArray.Empty + .Add(InheritanceRelationship.ImplementedInterface) + .Add(InheritanceRelationship.InheritedInterface) + .Add(InheritanceRelationship.ImplementedMember); + + private static readonly ImmutableArray s_relationships_Shown_As_I_Down_Arrow + = ImmutableArray.Empty + .Add(InheritanceRelationship.ImplementingType) + .Add(InheritanceRelationship.ImplementingMember); + + private static readonly ImmutableArray s_relationships_Shown_As_O_Up_Arrow + = ImmutableArray.Empty + .Add(InheritanceRelationship.BaseType) + .Add(InheritanceRelationship.OverriddenMember); + + private static readonly ImmutableArray s_relationships_Shown_As_O_Down_Arrow + = ImmutableArray.Empty + .Add(InheritanceRelationship.DerivedType) + .Add(InheritanceRelationship.OverridingMember); + /// /// Decide which moniker should be shown. /// public static ImageMoniker GetMoniker(InheritanceRelationship inheritanceRelationship) { // If there are multiple targets and we have the corresponding compound image, use it - if (inheritanceRelationship.HasFlag(InheritanceRelationship.ImplementingOverriding)) + if (s_relationships_Shown_As_I_Up_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag)) + && s_relationships_Shown_As_O_Down_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag))) { - return KnownMonikers.ImplementingOverriding; + return KnownMonikers.ImplementingOverridden; } - if (inheritanceRelationship.HasFlag(InheritanceRelationship.ImplementingOverridden)) + if (s_relationships_Shown_As_I_Up_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag)) + && s_relationships_Shown_As_O_Up_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag))) { - return KnownMonikers.ImplementingOverridden; + return KnownMonikers.ImplementingOverriding; } // Otherwise, show the image based on this preference - if (inheritanceRelationship.HasFlag(InheritanceRelationship.Implemented)) + if (s_relationships_Shown_As_I_Up_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag))) { - return KnownMonikers.Implemented; + return KnownMonikers.Implementing; } - if (inheritanceRelationship.HasFlag(InheritanceRelationship.Implementing)) + if (s_relationships_Shown_As_I_Down_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag))) { - return KnownMonikers.Implementing; + return KnownMonikers.Implemented; } - if (inheritanceRelationship.HasFlag(InheritanceRelationship.Overridden)) + if (s_relationships_Shown_As_O_Up_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag))) { - return KnownMonikers.Overridden; + return KnownMonikers.Overriding; } - if (inheritanceRelationship.HasFlag(InheritanceRelationship.Overriding)) + if (s_relationships_Shown_As_O_Down_Arrow.Any(flag => inheritanceRelationship.HasFlag(flag))) { - return KnownMonikers.Overriding; + return KnownMonikers.Overridden; } // The relationship is None. Don't know what image should be shown, throws throw ExceptionUtilities.UnexpectedValue(inheritanceRelationship); } - /// - /// Create the view models for the inheritance targets of a single member. - /// There are two cases: - /// 1. If all the targets have the same inheritance relationship. It would be an array of TargetViewModel - /// e.g. - /// Target1ViewModel - /// Target2ViewModel - /// Target3ViewModel - /// - /// 2. If targets belongs to different inheritance group. It would be grouped. - /// e.g. - /// Header1ViewModel - /// Target1ViewModel - /// Target2ViewModel - /// Header2ViewModel - /// Target1ViewModel - /// Target2ViewModel - /// public static ImmutableArray CreateMenuItemViewModelsForSingleMember(ImmutableArray targets) - { - var targetsByRelationship = targets.OrderBy(target => target.DisplayName).GroupBy(target => target.RelationToMember) - .ToImmutableDictionary( - keySelector: grouping => grouping.Key, - elementSelector: grouping => grouping); - if (targetsByRelationship.Count == 1) - { - // If all targets have one relationship. - // e.g. interface IBar { void Bar(); } - // class A : IBar { void Bar() {} } - // class B : IBar { void Bar() {} } - // for 'IBar', the margin would be I↓. So header is not needed. - var (_, targetItems) = targetsByRelationship.Single(); - return targetItems.SelectAsArray(target => TargetMenuItemViewModel.Create(target, indent: false)).CastArray(); - } - else - { - // Otherwise, it means these targets has different relationship, - // these targets would be shown in group, and a header should be shown as the first item to indicate the relationship to user. - return targetsByRelationship.SelectMany(kvp => CreateMenuItemsWithHeader(kvp.Key, kvp.Value)).ToImmutableArray(); - } - } + => targets.OrderBy(target => target.DisplayName) + .GroupBy(target => target.RelationToMember) + .SelectMany(grouping => CreateMenuItemsWithHeader(grouping.Key, grouping)) + .ToImmutableArray(); /// /// Create the view models for the inheritance targets of multiple members - /// There are two cases: - /// 1. If all the targets have the same inheritance relationship. It would have this structure: - /// e.g. - /// MemberViewModel1 -> Target1ViewModel - /// Target2ViewModel - /// MemberViewModel2 -> Target4ViewModel - /// Target5ViewModel - /// - /// 2. If targets belongs to different inheritance group. It would be grouped. /// e.g. /// MemberViewModel1 -> HeaderViewModel /// Target1ViewModel @@ -126,17 +104,7 @@ public static ImmutableArray CreateMenuItemViewMod // For multiple members, check if all the targets have the same inheritance relationship. // If so, then don't add the header, because it is already indicated by the margin. // Otherwise, add the Header. - var set = members - .SelectMany(member => member.TargetItems.Select(item => item.RelationToMember)) - .ToImmutableHashSet(); - if (set.Count == 1) - { - return members.SelectAsArray(MemberMenuItemViewModel.CreateWithNoHeaderInTargets).CastArray(); - } - else - { - return members.SelectAsArray(MemberMenuItemViewModel.CreateWithHeaderInTargets).CastArray(); - } + return members.SelectAsArray(MemberMenuItemViewModel.CreateWithHeaderInTargets).CastArray(); } public static ImmutableArray CreateMenuItemsWithHeader( @@ -146,10 +114,15 @@ public static ImmutableArray CreateMenuItemsWithHe using var _ = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var builder); var displayContent = relationship switch { - InheritanceRelationship.Implemented => ServicesVSResources.Implemented_members, - InheritanceRelationship.Implementing => ServicesVSResources.Implementing_members, - InheritanceRelationship.Overriding => ServicesVSResources.Overriding_members, - InheritanceRelationship.Overridden => ServicesVSResources.Overridden_members, + InheritanceRelationship.ImplementedInterface => ServicesVSResources.Implemented_interfaces, + InheritanceRelationship.BaseType => ServicesVSResources.Base_Types, + InheritanceRelationship.DerivedType => ServicesVSResources.Derived_types, + InheritanceRelationship.InheritedInterface => ServicesVSResources.Inherited_interfaces, + InheritanceRelationship.ImplementingType => ServicesVSResources.Implementing_types, + InheritanceRelationship.ImplementedMember => ServicesVSResources.Implemented_members, + InheritanceRelationship.OverriddenMember => ServicesVSResources.Overridden_members, + InheritanceRelationship.OverridingMember => ServicesVSResources.Overriding_members, + InheritanceRelationship.ImplementingMember => ServicesVSResources.Implementing_members, _ => throw ExceptionUtilities.UnexpectedValue(relationship) }; @@ -157,7 +130,7 @@ public static ImmutableArray CreateMenuItemsWithHe builder.Add(headerViewModel); foreach (var targetItem in targets) { - builder.Add(TargetMenuItemViewModel.Create(targetItem, indent: true)); + builder.Add(TargetMenuItemViewModel.Create(targetItem)); } return builder.ToImmutable(); diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs index 1a8be7aeef315..7f7357b82ad73 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs @@ -15,8 +15,10 @@ using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; +using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; @@ -45,6 +47,8 @@ public InheritanceMarginTaggerProvider( protected override TaggerDelay EventChangeDelay => TaggerDelay.OnIdle; + private bool? _experimentEnabled = null; + protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) // Because we use frozen-partial documents for semantic classification, we may end up with incomplete // semantics (esp. during solution load). Because of this, we also register to hear when the full @@ -84,8 +88,16 @@ protected override async Task ProduceTagsAsync( var cancellationToken = context.CancellationToken; var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var featureEnabled = options.GetOption(FeatureOnOffOptions.ShowInheritanceMargin); - if (!featureEnabled) + + var optionIsChecked = options.GetOption(FeatureOnOffOptions.ShowInheritanceMargin); + if (_experimentEnabled is null) + { + var experimentationService = document.Project.Solution.Workspace.Services.GetRequiredService(); + _experimentEnabled = experimentationService.IsExperimentEnabled(WellKnownExperimentNames.InheritanceMargin); + } + + var shouldEnableFeature = optionIsChecked == true || (_experimentEnabled == true && optionIsChecked == null); + if (!shouldEnableFeature) { return; } @@ -99,10 +111,14 @@ protected override async Task ProduceTagsAsync( return; } - var inheritanceMemberItems = await inheritanceMarginInfoService.GetInheritanceMemberItemsAsync( + var inheritanceMemberItems = ImmutableArray.Empty; + using (Logger.LogBlock(FunctionId.InheritanceMargin_GetInheritanceMemberItems, cancellationToken, LogLevel.Information)) + { + inheritanceMemberItems = await inheritanceMarginInfoService.GetInheritanceMemberItemsAsync( document, spanToTag.SnapshotSpan.Span.ToTextSpan(), cancellationToken).ConfigureAwait(false); + } if (inheritanceMemberItems.IsEmpty) { diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs index c1cc072ae084f..ae8e6d1a71fd8 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs @@ -8,13 +8,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMarg { /// /// The view model used for the header of TargetMenuItemViewModel. - /// It is used when the context menu contains targets having multiple inheritance relationship. - /// In such case, this would be shown as a header for a group of targets. /// e.g. - /// 'I↓ Implemented members' + /// 'I↓ Implementing members' /// Method 'Bar' - /// 'I↑ Implementing members' - /// Method 'Foo' /// internal class HeaderMenuItemViewModel : InheritanceMenuItemViewModel { diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml index c93bf9692e9eb..800af9d3f31d2 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml @@ -115,7 +115,7 @@ - diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs index d2003b6644329..5915c0e02ef00 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph @@ -26,6 +27,7 @@ internal partial class InheritanceMargin private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter; private readonly IUIThreadOperationExecutor _operationExecutor; private readonly Workspace _workspace; + private readonly IWpfTextView _textView; public InheritanceMargin( IThreadingContext threadingContext, @@ -34,14 +36,18 @@ public InheritanceMargin( IClassificationFormatMap classificationFormatMap, IUIThreadOperationExecutor operationExecutor, InheritanceMarginTag tag, - double scaleFactor) + IWpfTextView textView) { _threadingContext = threadingContext; _streamingFindUsagesPresenter = streamingFindUsagesPresenter; _workspace = tag.Workspace; _operationExecutor = operationExecutor; + _textView = textView; InitializeComponent(); + // ZoomLevel of textView is percentage based. (e.g. 20 -> 400 means 20% -> 400%) + // and the scaleFactor of CrispImage is 1 based. (e.g 1 means 100%) + var scaleFactor = textView.ZoomLevel; var viewModel = InheritanceMarginViewModel.Create(classificationTypeMap, classificationFormatMap, tag, scaleFactor); DataContext = viewModel; ContextMenu.DataContext = viewModel; @@ -102,6 +108,9 @@ private void InheritanceMargin_OnMouseLeave(object sender, MouseEventArgs e) private void ContextMenu_OnClose(object sender, RoutedEventArgs e) { ResetBorderToInitialColor(); + // Move the focus back to textView when the context menu is closed. + // It ensures the focus won't be left at the margin + ResetFocus(); } private void ContextMenu_OnOpen(object sender, RoutedEventArgs e) @@ -110,13 +119,16 @@ private void ContextMenu_OnOpen(object sender, RoutedEventArgs e) && inheritanceMarginViewModel.MenuItemViewModels.Any(vm => vm is TargetMenuItemViewModel)) { // We have two kinds of context menu. e.g. - // 1. [margin] -> Target1 + // 1. [margin] -> Header + // Target1 // Target2 // Target3 // - // 2. [margin] -> method Bar -> Target1 + // 2. [margin] -> method Bar -> Header + // -> Target1 // -> Target2 - // -> method Foo -> Target3 + // -> method Foo -> Header + // -> Target3 // -> Target4 // If the first level of the context menu contains a TargetMenuItemViewModel, it means here it is case 1, // user is viewing the targets menu. @@ -134,5 +146,17 @@ private void ResetBorderToInitialColor() this.Background = Brushes.Transparent; this.BorderBrush = Brushes.Transparent; } + + private void ResetFocus() + { + if (!_textView.HasAggregateFocus) + { + var visualElement = _textView.VisualElement; + if (visualElement.Focusable) + { + Keyboard.Focus(visualElement); + } + } + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs index b3c55621dda71..49186e2bb773f 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs @@ -41,19 +41,6 @@ public MemberMenuItemViewModel( Targets = targets; } - public static MemberMenuItemViewModel CreateWithNoHeaderInTargets(InheritanceMarginItem member) - { - var displayName = member.DisplayTexts.JoinText(); - return new MemberMenuItemViewModel( - displayName, - member.Glyph.GetImageMoniker(), - displayName, - member.TargetItems - .OrderBy(item => item.DisplayName) - .SelectAsArray(item => TargetMenuItemViewModel.Create(item, indent: false)) - .CastArray()); - } - public static MemberMenuItemViewModel CreateWithHeaderInTargets(InheritanceMarginItem member) { var displayName = member.DisplayTexts.JoinText(); diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs index d8b553509c76a..a52160126b47b 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Windows; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.InheritanceMargin; @@ -15,45 +14,22 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMarg /// internal class TargetMenuItemViewModel : InheritanceMenuItemViewModel { - /// - /// The margin for the default case. - /// - private static readonly Thickness s_defaultMargin = new Thickness(4, 1, 4, 1); - - /// - /// The margin used when this target item needs to be indented when the target is shown with the header. - /// e.g. - /// 'I↓ Implemented members' - /// Method 'Bar' - /// 'I↑ Implementing members' - /// Method 'Foo' - /// It is 22 because the default left margin is 4, and we want to keep the same indentation margin same as solution explorer, which is 18. - /// - private static readonly Thickness s_indentMargin = new Thickness(22, 1, 4, 1); - /// /// DefinitionItem used for navigation. /// public DefinitionItem DefinitionItem { get; } - /// - /// Margin for the image moniker. - /// - public Thickness Margin { get; } - // Internal for testing purpose internal TargetMenuItemViewModel( string displayContent, ImageMoniker imageMoniker, string automationName, - DefinitionItem definitionItem, - Thickness margin) : base(displayContent, imageMoniker, automationName) + DefinitionItem definitionItem) : base(displayContent, imageMoniker, automationName) { DefinitionItem = definitionItem; - Margin = margin; } - public static TargetMenuItemViewModel Create(InheritanceTargetItem target, bool indent) + public static TargetMenuItemViewModel Create(InheritanceTargetItem target) { var displayContent = target.DisplayName; var imageMoniker = target.Glyph.GetImageMoniker(); @@ -61,8 +37,7 @@ public static TargetMenuItemViewModel Create(InheritanceTargetItem target, bool displayContent, imageMoniker, displayContent, - target.DefinitionItem, - indent ? s_indentMargin : s_defaultMargin); + target.DefinitionItem); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs index ea964c8b3a819..522f8835ee73b 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs @@ -204,7 +204,7 @@ private void DiagnosticService_DiagnosticsUpdated(Solution? solution, DocumentId => Uri.Compare(uri1, uri2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // internal for testing purposes - internal async Task ProcessDiagnosticUpdatedBatchAsync( + internal async ValueTask ProcessDiagnosticUpdatedBatchAsync( IDiagnosticService? diagnosticService, ImmutableArray documentIds, CancellationToken cancellationToken) { if (diagnosticService == null) diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs index 8254d8556ad95..accf5a857fbf3 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -31,7 +32,7 @@ internal class VsCodeWindowManager : IVsCodeWindowManager, IVsCodeWindowEvents private readonly IThreadingContext _threadingContext; private readonly IAsynchronousOperationListener _asynchronousOperationListener; - private INavigationBarController? _navigationBarController; + private IDisposable? _navigationBarController; private IVsDropdownBarClient? _dropdownBarClient; private IOptionService? _optionService; private WorkspaceRegistration? _workspaceRegistration; @@ -73,8 +74,6 @@ private async Task UpdateWorkspaceAsync() // There's a new workspace, so make sure we unsubscribe from the old workspace option changes and subscribe to new. UpdateOptionChangedSource(_workspaceRegistration.Workspace); - _navigationBarController?.SetWorkspace(_workspaceRegistration.Workspace); - // Trigger a check to see if the dropdown should be added / removed now that the buffer is in a different workspace. AddOrRemoveDropdown(); } @@ -209,12 +208,11 @@ private void AdddropdownBar(IVsDropdownBarManager dropdownManager) var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(buffer); var controllerFactoryService = _languageService.Package.ComponentModel.GetService(); var newController = controllerFactoryService.CreateController(navigationBarClient, textBuffer); - newController.SetWorkspace(_workspaceRegistration?.Workspace); var hr = dropdownManager.AddDropdownBar(cCombos: 3, pClient: navigationBarClient); if (ErrorHandler.Failed(hr)) { - newController.Disconnect(); + newController.Dispose(); ErrorHandler.ThrowOnFailure(hr); } @@ -229,7 +227,7 @@ private void RemoveDropdownBar(IVsDropdownBarManager dropdownManager) { if (_navigationBarController != null) { - _navigationBarController.Disconnect(); + _navigationBarController.Dispose(); _navigationBarController = null; } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphBuilder.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphBuilder.cs index 4a036f367df50..ac4ccc94536f2 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphBuilder.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphBuilder.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Elfie.Model; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.GraphModel; @@ -33,8 +34,6 @@ internal sealed partial class GraphBuilder private readonly ISet _createdNodes = new HashSet(); private readonly IList> _deferredPropertySets = new List>(); - private readonly CancellationToken _cancellationToken; - private readonly Dictionary _nodeToContextProjectMap = new(); private readonly Dictionary _nodeToContextDocumentMap = new(); private readonly Dictionary _nodeToSymbolMap = new(); @@ -44,34 +43,34 @@ internal sealed partial class GraphBuilder /// private readonly Solution _solution; - public GraphBuilder(Solution solution, CancellationToken cancellationToken) + public GraphBuilder(Solution solution) { _solution = solution; - _cancellationToken = cancellationToken; } - public static async Task CreateForInputNodesAsync(Solution solution, IEnumerable inputNodes, CancellationToken cancellationToken) + public static async Task CreateForInputNodesAsync( + Solution solution, IEnumerable inputNodes, CancellationToken cancellationToken) { - var builder = new GraphBuilder(solution, cancellationToken); + var builder = new GraphBuilder(solution); foreach (var inputNode in inputNodes) { if (inputNode.HasCategory(CodeNodeCategories.File)) { - builder.PopulateMapsForFileInputNode(inputNode); + builder.PopulateMapsForFileInputNode(inputNode, cancellationToken); } else if (!inputNode.HasCategory(CodeNodeCategories.SourceLocation)) { - await builder.PopulateMapsForSymbolInputNodeAsync(inputNode).ConfigureAwait(false); + await builder.PopulateMapsForSymbolInputNodeAsync(inputNode, cancellationToken).ConfigureAwait(false); } } return builder; } - private void PopulateMapsForFileInputNode(GraphNode inputNode) + private void PopulateMapsForFileInputNode(GraphNode inputNode, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { var projectPath = inputNode.Id.GetNestedValueByName(CodeGraphNodeIdName.Assembly); var filePath = inputNode.Id.GetNestedValueByName(CodeGraphNodeIdName.File); @@ -101,9 +100,9 @@ private void PopulateMapsForFileInputNode(GraphNode inputNode) } } - private async Task PopulateMapsForSymbolInputNodeAsync(GraphNode inputNode) + private async Task PopulateMapsForSymbolInputNodeAsync(GraphNode inputNode, CancellationToken cancellationToken) { - using (await _gate.DisposableWaitAsync(_cancellationToken).ConfigureAwait(false)) + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { var projectId = (ProjectId)inputNode[RoslynGraphProperties.ContextProjectId]; if (projectId == null) @@ -119,9 +118,9 @@ private async Task PopulateMapsForSymbolInputNodeAsync(GraphNode inputNode) _nodeToContextProjectMap.Add(inputNode, project); - var compilation = await project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var symbolId = (SymbolKey?)inputNode[RoslynGraphProperties.SymbolId]; - var symbol = symbolId.Value.Resolve(compilation).Symbol; + var symbol = symbolId.Value.Resolve(compilation, cancellationToken: cancellationToken).Symbol; if (symbol != null) { _nodeToSymbolMap.Add(inputNode, symbol); @@ -139,9 +138,9 @@ private async Task PopulateMapsForSymbolInputNodeAsync(GraphNode inputNode) } } - public Project GetContextProject(GraphNode node) + public Project GetContextProject(GraphNode node, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { _nodeToContextProjectMap.TryGetValue(node, out var project); return project; @@ -154,32 +153,37 @@ public ProjectId GetContextProjectId(Project project, ISymbol symbol) return thisProject.Id; } - public Document GetContextDocument(GraphNode node) + public Document GetContextDocument(GraphNode node, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { _nodeToContextDocumentMap.TryGetValue(node, out var document); return document; } } - public ISymbol GetSymbol(GraphNode node) + public ISymbol GetSymbol(GraphNode node, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { _nodeToSymbolMap.TryGetValue(node, out var symbol); return symbol; } } - public Task AddNodeAsync(ISymbol symbol, GraphNode relatedNode) + public Task AddNodeAsync(ISymbol symbol, GraphNode relatedNode, CancellationToken cancellationToken) { // The lack of a lock here is acceptable, since each of the functions lock, and GetContextProject/GetContextDocument // never change for the same input. - return AddNodeAsync(symbol, GetContextProject(relatedNode), GetContextDocument(relatedNode)); + return AddNodeAsync( + symbol, + GetContextProject(relatedNode, cancellationToken), + GetContextDocument(relatedNode, cancellationToken), + cancellationToken); } - public async Task AddNodeAsync(ISymbol symbol, Project contextProject, Document contextDocument) + public async Task AddNodeAsync( + ISymbol symbol, Project contextProject, Document contextDocument, CancellationToken cancellationToken) { // Figure out what the location for this node should be. We'll arbitrarily pick the // first one, unless we have a contextDocument to restrict it @@ -187,7 +191,7 @@ public async Task AddNodeAsync(ISymbol symbol, Project contextProject if (contextDocument != null) { - var syntaxTree = await contextDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); + var syntaxTree = await contextDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); // If we have one in that tree, use it preferredLocation = symbol.Locations.FirstOrDefault(l => l.SourceTree == syntaxTree) ?? preferredLocation; @@ -196,18 +200,18 @@ public async Task AddNodeAsync(ISymbol symbol, Project contextProject // We may need to look up source code within this solution if (preferredLocation == null && symbol.Locations.Any(loc => loc.IsInMetadata)) { - var newSymbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, contextProject.Solution, _cancellationToken).ConfigureAwait(false); + var newSymbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, contextProject.Solution, cancellationToken).ConfigureAwait(false); if (newSymbol != null) { preferredLocation = newSymbol.Locations.Where(loc => loc.IsInSource).FirstOrDefault(); } } - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { - var node = await GetOrCreateNodeAsync(_graph, symbol, _solution, _cancellationToken).ConfigureAwait(false); + var node = await GetOrCreateNodeAsync(_graph, symbol, _solution, cancellationToken).ConfigureAwait(false); - node[RoslynGraphProperties.SymbolId] = (SymbolKey?)symbol.GetSymbolKey(); + node[RoslynGraphProperties.SymbolId] = (SymbolKey?)symbol.GetSymbolKey(cancellationToken); node[RoslynGraphProperties.ContextProjectId] = GetContextProjectId(contextProject, symbol); node[RoslynGraphProperties.ExplicitInterfaceImplementations] = symbol.ExplicitInterfaceImplementations().Select(s => s.GetSymbolKey()).ToList(); node[RoslynGraphProperties.DeclaredAccessibility] = symbol.DeclaredAccessibility; @@ -665,17 +669,17 @@ private static async Task GetOrCreateNodeForEventAsync(Graph graph, I return node; } - public void AddLink(GraphNode from, GraphCategory category, GraphNode to) + public void AddLink(GraphNode from, GraphCategory category, GraphNode to, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { _graph.Links.GetOrCreate(from, to).AddCategory(category); } } - public GraphNode AddNodeForDocument(Document document) + public GraphNode AddNodeForDocument(Document document, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { var id = GraphNodeIdCreation.GetIdForDocument(document); @@ -690,9 +694,101 @@ public GraphNode AddNodeForDocument(Document document) } } - public void ApplyToGraph(Graph graph) + public async Task CreateNodeAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + var document = result.NavigableItem.Document; + var project = document.Project; + + // If it doesn't belong to a document or project we can navigate to, then ignore entirely. + if (document.FilePath == null || project.FilePath == null) + return null; + + var category = result.Kind switch + { + NavigateToItemKind.Class => CodeNodeCategories.Class, + NavigateToItemKind.Delegate => CodeNodeCategories.Delegate, + NavigateToItemKind.Enum => CodeNodeCategories.Enum, + NavigateToItemKind.Interface => CodeNodeCategories.Interface, + NavigateToItemKind.Module => CodeNodeCategories.Module, + NavigateToItemKind.Structure => CodeNodeCategories.Struct, + NavigateToItemKind.Method => CodeNodeCategories.Method, + NavigateToItemKind.Property => CodeNodeCategories.Property, + NavigateToItemKind.Event => CodeNodeCategories.Event, + NavigateToItemKind.Constant or + NavigateToItemKind.EnumItem or + NavigateToItemKind.Field => CodeNodeCategories.Field, + _ => null, + }; + + // If it's not a category that progression understands, then ignore. + if (category == null) + return null; + + // Get or make a node for this symbol's containing document that will act as the parent node in the UI. + var documentNode = this.AddNodeForDocument(document, cancellationToken); + + // For purposes of keying this node, just use the display text we will show. In practice, outside of error + // scenarios this will be unique and suitable as an ID (esp. as these names are joined with their parent + // document name to form the full ID). + var label = result.NavigableItem.DisplayTaggedParts.JoinText(); + var id = documentNode.Id.Add(GraphNodeId.GetLiteral(label)); + + // If we already have a node that matches this (say there are multiple identical sibling symbols in an error + // situation). We just ignore the second match. + var existing = _graph.Nodes.Get(id); + if (existing != null) + return null; + + var symbolNode = _graph.Nodes.GetOrCreate(id); + + symbolNode.Label = label; + symbolNode.AddCategory(category); + symbolNode[DgmlNodeProperties.Icon] = GetIconString(result.NavigableItem.Glyph); + symbolNode[RoslynGraphProperties.ContextDocumentId] = document.Id; + symbolNode[RoslynGraphProperties.ContextProjectId] = project.Id; + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var span = text.Lines.GetLinePositionSpan(NavigateToUtilities.GetBoundedSpan(result.NavigableItem, text)); + + symbolNode[CodeNodeProperties.SourceLocation] = new SourceLocation( + document.FilePath, + new Position(span.Start.Line, span.Start.Character), + new Position(span.End.Line, span.End.Character)); + + this.AddLink(documentNode, GraphCommonSchema.Contains, symbolNode, cancellationToken); + + return symbolNode; + } + + private static string GetIconString(Glyph glyph) + { + var groupName = glyph switch + { + Glyph.ClassPublic or Glyph.ClassProtected or Glyph.ClassPrivate or Glyph.ClassInternal => "Class", + Glyph.ConstantPublic or Glyph.ConstantProtected or Glyph.ConstantPrivate or Glyph.ConstantInternal => "Field", + Glyph.DelegatePublic or Glyph.DelegateProtected or Glyph.DelegatePrivate or Glyph.DelegateInternal => "Delegate", + Glyph.EnumPublic or Glyph.EnumProtected or Glyph.EnumPrivate or Glyph.EnumInternal => "Enum", + Glyph.EnumMemberPublic or Glyph.EnumMemberProtected or Glyph.EnumMemberPrivate or Glyph.EnumMemberInternal => "EnumMember", + Glyph.ExtensionMethodPublic or Glyph.ExtensionMethodProtected or Glyph.ExtensionMethodPrivate or Glyph.ExtensionMethodInternal => "Method", + Glyph.EventPublic or Glyph.EventProtected or Glyph.EventPrivate or Glyph.EventInternal => "Event", + Glyph.FieldPublic or Glyph.FieldProtected or Glyph.FieldPrivate or Glyph.FieldInternal => "Field", + Glyph.InterfacePublic or Glyph.InterfaceProtected or Glyph.InterfacePrivate or Glyph.InterfaceInternal => "Interface", + Glyph.MethodPublic or Glyph.MethodProtected or Glyph.MethodPrivate or Glyph.MethodInternal => "Method", + Glyph.ModulePublic or Glyph.ModuleProtected or Glyph.ModulePrivate or Glyph.ModuleInternal => "Module", + Glyph.PropertyPublic or Glyph.PropertyProtected or Glyph.PropertyPrivate or Glyph.PropertyInternal => "Property", + Glyph.StructurePublic or Glyph.StructureProtected or Glyph.StructurePrivate or Glyph.StructureInternal => "Structure", + _ => null, + }; + + if (groupName == null) + return null; + + return IconHelper.GetIconName(groupName, GlyphExtensions.GetAccessibility(GlyphTags.GetTags(glyph))); + } + + public void ApplyToGraph(Graph graph, CancellationToken cancellationToken) + { + using (_gate.DisposableWait(cancellationToken)) { using var graphTransaction = new GraphTransactionScope(); graph.Merge(this.Graph); @@ -707,9 +803,9 @@ public void ApplyToGraph(Graph graph) } } - public void AddDeferredPropertySet(GraphNode node, GraphProperty property, object value) + public void AddDeferredPropertySet(GraphNode node, GraphProperty property, object value, CancellationToken cancellationToken) { - using (_gate.DisposableWait()) + using (_gate.DisposableWait(cancellationToken)) { _deferredPropertySets.Add(Tuple.Create(node, property, value)); } @@ -723,25 +819,11 @@ public Graph Graph } } - public IEnumerable CreatedNodes - { - get - { - using (_gate.DisposableWait()) - { - return _createdNodes.ToArray(); - } - } - } - - public IEnumerable> DeferredPropertySets + public IEnumerable GetCreatedNodes(CancellationToken cancellationToken) { - get + using (_gate.DisposableWait(cancellationToken)) { - using (_gate.DisposableWait()) - { - return _deferredPropertySets.ToArray(); - } + return _createdNodes.ToArray(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphProvider.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphProvider.cs index 9ff632119c43f..616538256944c 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphProvider.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.GraphModel; +using Microsoft.VisualStudio.GraphModel.CodeSchema; using Microsoft.VisualStudio.GraphModel.Schemas; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Progression; @@ -25,6 +26,7 @@ internal class AbstractGraphProvider : IGraphProvider private readonly IThreadingContext _threadingContext; private readonly IGlyphService _glyphService; private readonly IServiceProvider _serviceProvider; + private readonly IAsynchronousOperationListener _asyncListener; private readonly Workspace _workspace; private readonly GraphQueryManager _graphQueryManager; @@ -40,9 +42,9 @@ protected AbstractGraphProvider( _threadingContext = threadingContext; _glyphService = glyphService; _serviceProvider = serviceProvider; - var asyncListener = listenerProvider.GetListener(FeatureAttribute.GraphProvider); + _asyncListener = listenerProvider.GetListener(FeatureAttribute.GraphProvider); _workspace = workspace; - _graphQueryManager = new GraphQueryManager(workspace, asyncListener); + _graphQueryManager = new GraphQueryManager(workspace, _asyncListener); } private void EnsureInitialized() @@ -57,7 +59,10 @@ private void EnsureInitialized() _initialized = true; } - internal static List GetGraphQueries(IGraphContext context) + internal static List GetGraphQueries( + IGraphContext context, + IThreadingContext threadingContext, + IAsynchronousOperationListener asyncListener) { var graphQueries = new List(); @@ -134,7 +139,8 @@ internal static List GetGraphQueries(IGraphContext context) // WARNING: searchParameters.SearchQuery returns an IVsSearchQuery object, which // is a COM type. Therefore, it's probably best to grab the values we want now // rather than get surprised by COM marshalling later. - graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString)); + graphQueries.Add(new SearchGraphQuery( + searchParameters.SearchQuery.SearchString, threadingContext, asyncListener)); } } @@ -145,7 +151,7 @@ public void BeginGetGraphData(IGraphContext context) { EnsureInitialized(); - var graphQueries = GetGraphQueries(context); + var graphQueries = GetGraphQueries(context, _threadingContext, _asyncListener); if (graphQueries.Count > 0) { @@ -345,23 +351,21 @@ public T GetExtension(GraphObject graphObject, T previous) where T : class if (graphObject is GraphNode graphNode) { // If this is not a Roslyn node, bail out. - // TODO: The check here is to see if the SymbolId property exists on the node - // and if so, that's been created by us. However, eventually we'll want to extend - // this to other scenarios where C#\VB nodes that aren't created by us are passed in. - if (graphNode.GetValue(RoslynGraphProperties.SymbolId) == null) + if (graphNode.GetValue(RoslynGraphProperties.ContextProjectId) == null) + return null; + + // Has to have at least a symbolid, or source location to navigate to. + if (graphNode.GetValue(RoslynGraphProperties.SymbolId) == null && + graphNode.GetValue(CodeNodeProperties.SourceLocation).FileName == null) { return null; } if (typeof(T) == typeof(IGraphNavigateToItem)) - { return new GraphNavigatorExtension(_threadingContext, _workspace) as T; - } if (typeof(T) == typeof(IGraphFormattedLabel)) - { return new GraphFormattedLabelExtension() as T; - } } return null; diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/CallsGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/CallsGraphQuery.cs index e6d033200b98d..e88829a5de308 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/CallsGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/CallsGraphQuery.cs @@ -23,15 +23,15 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol != null) { foreach (var newSymbol in await GetCalledMethodSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); - var newNode = await graphBuilder.AddNodeAsync(newSymbol, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(node, CodeLinkCategories.Calls, newNode); + var newNode = await graphBuilder.AddNodeAsync(newSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(node, CodeLinkCategories.Calls, newNode, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsChildrenGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsChildrenGraphQuery.cs index 23a5071dbbf5d..e5a95567036e8 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsChildrenGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsChildrenGraphQuery.cs @@ -25,21 +25,21 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c { if (!cancellationToken.IsCancellationRequested) { - var symbol = graphBuilder.GetSymbol(node); - + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol != null) { var containsChildren = SymbolContainment.GetContainedSymbols(symbol).Any(); - graphBuilder.AddDeferredPropertySet(node, DgmlNodeProperties.ContainsChildren, containsChildren); + graphBuilder.AddDeferredPropertySet( + node, DgmlNodeProperties.ContainsChildren, containsChildren, cancellationToken); } else if (node.HasCategory(CodeNodeCategories.File)) { - var document = graphBuilder.GetContextDocument(node); - + var document = graphBuilder.GetContextDocument(node, cancellationToken); if (document != null) { var childNodes = await SymbolContainment.GetContainedSyntaxNodesAsync(document, cancellationToken).ConfigureAwait(false); - graphBuilder.AddDeferredPropertySet(node, DgmlNodeProperties.ContainsChildren, childNodes.Any()); + graphBuilder.AddDeferredPropertySet( + node, DgmlNodeProperties.ContainsChildren, childNodes.Any(), cancellationToken); } else { @@ -72,7 +72,8 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c // also perform the check. if (path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".vb", StringComparison.OrdinalIgnoreCase)) { - graphBuilder.AddDeferredPropertySet(node, DgmlNodeProperties.ContainsChildren, false); + graphBuilder.AddDeferredPropertySet( + node, DgmlNodeProperties.ContainsChildren, value: false, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsGraphQuery.cs index 9ae7defcd84e2..eb9ad620eecca 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ContainsGraphQuery.cs @@ -33,30 +33,30 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c { cancellationToken.ThrowIfCancellationRequested(); - var symbol = graphBuilder.GetSymbol(node); - + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol != null) { foreach (var newSymbol in SymbolContainment.GetContainedSymbols(symbol)) { cancellationToken.ThrowIfCancellationRequested(); - var newNode = await graphBuilder.AddNodeAsync(newSymbol, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(node, GraphCommonSchema.Contains, newNode); + var newNode = await graphBuilder.AddNodeAsync( + newSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(node, GraphCommonSchema.Contains, newNode, cancellationToken); } } else if (node.HasCategory(CodeNodeCategories.File)) { - var document = graphBuilder.GetContextDocument(node); - + var document = graphBuilder.GetContextDocument(node, cancellationToken); if (document != null) { foreach (var newSymbol in await SymbolContainment.GetContainedSymbolsAsync(document, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); - var newNode = await graphBuilder.AddNodeAsync(newSymbol, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(node, GraphCommonSchema.Contains, newNode); + var newNode = await graphBuilder.AddNodeAsync( + newSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(node, GraphCommonSchema.Contains, newNode, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementedByGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementedByGraphQuery.cs index 27fa385ab03f9..1e022e65eedf0 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementedByGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementedByGraphQuery.cs @@ -24,7 +24,7 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol is INamedTypeSymbol || symbol is IMethodSymbol || symbol is IPropertySymbol || @@ -34,8 +34,10 @@ symbol is IPropertySymbol || foreach (var implementation in implementations) { - var symbolNode = await graphBuilder.AddNodeAsync(implementation, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, CodeLinkCategories.Implements, node); + var symbolNode = await graphBuilder.AddNodeAsync( + implementation, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink( + symbolNode, CodeLinkCategories.Implements, node, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementsGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementsGraphQuery.cs index 4c32c1ea06332..6866d33bc520d 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementsGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ImplementsGraphQuery.cs @@ -26,19 +26,19 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol is INamedTypeSymbol namedType) { var implementedSymbols = ImmutableArray.CastUp(namedType.AllInterfaces); - await AddImplementedSymbolsAsync(graphBuilder, node, implementedSymbols).ConfigureAwait(false); + await AddImplementedSymbolsAsync(graphBuilder, node, implementedSymbols, cancellationToken).ConfigureAwait(false); } else if (symbol is IMethodSymbol || symbol is IPropertySymbol || symbol is IEventSymbol) { var implements = await SymbolFinder.FindImplementedInterfaceMembersArrayAsync(symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); - await AddImplementedSymbolsAsync(graphBuilder, node, implements).ConfigureAwait(false); + await AddImplementedSymbolsAsync(graphBuilder, node, implements, cancellationToken).ConfigureAwait(false); } } @@ -47,13 +47,13 @@ symbol is IPropertySymbol || } private static async Task AddImplementedSymbolsAsync( - GraphBuilder graphBuilder, GraphNode node, - ImmutableArray implementedSymbols) + GraphBuilder graphBuilder, GraphNode node, ImmutableArray implementedSymbols, CancellationToken cancellationToken) { foreach (var interfaceType in implementedSymbols) { - var interfaceTypeNode = await graphBuilder.AddNodeAsync(interfaceType, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(node, CodeLinkCategories.Implements, interfaceTypeNode); + var interfaceTypeNode = await graphBuilder.AddNodeAsync( + interfaceType, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(node, CodeLinkCategories.Implements, interfaceTypeNode, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritedByGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritedByGraphQuery.cs index a2a326243e3fa..cf991313e399d 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritedByGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritedByGraphQuery.cs @@ -22,7 +22,7 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (!(symbol is INamedTypeSymbol namedType)) continue; @@ -33,8 +33,8 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var derivedType in derivedTypes) { var symbolNode = await graphBuilder.AddNodeAsync( - derivedType, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, CodeLinkCategories.InheritsFrom, node); + derivedType, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(symbolNode, CodeLinkCategories.InheritsFrom, node, cancellationToken); } } else if (namedType.TypeKind == TypeKind.Interface) @@ -46,8 +46,8 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var derivedType in implementingClassesAndStructs.Concat(derivedInterfaces)) { var symbolNode = await graphBuilder.AddNodeAsync( - derivedType, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, CodeLinkCategories.InheritsFrom, node); + derivedType, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(symbolNode, CodeLinkCategories.InheritsFrom, node, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritsGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritsGraphQuery.cs index 31488b032efa6..6845b7171d254 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritsGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/InheritsGraphQuery.cs @@ -33,24 +33,24 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in nodesToProcess) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol is INamedTypeSymbol namedType) { if (namedType.BaseType != null) { var baseTypeNode = await graphBuilder.AddNodeAsync( - namedType.BaseType, relatedNode: node).ConfigureAwait(false); + namedType.BaseType, relatedNode: node, cancellationToken).ConfigureAwait(false); newNodes.Add(baseTypeNode); - graphBuilder.AddLink(node, CodeLinkCategories.InheritsFrom, baseTypeNode); + graphBuilder.AddLink(node, CodeLinkCategories.InheritsFrom, baseTypeNode, cancellationToken); } else if (namedType.TypeKind == TypeKind.Interface && !namedType.OriginalDefinition.AllInterfaces.IsEmpty) { foreach (var baseNode in namedType.OriginalDefinition.AllInterfaces.Distinct()) { var baseTypeNode = await graphBuilder.AddNodeAsync( - baseNode, relatedNode: node).ConfigureAwait(false); + baseNode, relatedNode: node, cancellationToken).ConfigureAwait(false); newNodes.Add(baseTypeNode); - graphBuilder.AddLink(node, CodeLinkCategories.InheritsFrom, baseTypeNode); + graphBuilder.AddLink(node, CodeLinkCategories.InheritsFrom, baseTypeNode, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsCalledByGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsCalledByGraphQuery.cs index bff167a6f4b28..d5198f76bd6ba 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsCalledByGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsCalledByGraphQuery.cs @@ -25,15 +25,16 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol != null) { var callers = await SymbolFinder.FindCallersAsync(symbol, solution, cancellationToken).ConfigureAwait(false); foreach (var caller in callers.Where(c => c.IsDirect)) { - var callerNode = await graphBuilder.AddNodeAsync(caller.CallingSymbol, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(callerNode, CodeLinkCategories.Calls, node); + var callerNode = await graphBuilder.AddNodeAsync( + caller.CallingSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(callerNode, CodeLinkCategories.Calls, node, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsUsedByGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsUsedByGraphQuery.cs index 99495e398949b..f9ad48d359da4 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsUsedByGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/IsUsedByGraphQuery.cs @@ -26,13 +26,13 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); var references = await SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken).ConfigureAwait(false); foreach (var reference in references) { var referencedSymbol = reference.Definition; - var projectId = graphBuilder.GetContextProject(node).Id; + var projectId = graphBuilder.GetContextProject(node, cancellationToken).Id; var allLocations = referencedSymbol.Locations.Concat(reference.Locations.Select(r => r.Location)) .Where(l => l != null && l.IsInSource); @@ -40,7 +40,7 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var location in allLocations) { var locationNode = GetLocationNode(location, context, projectId, cancellationToken); - graphBuilder.AddLink(node, CodeLinkCategories.SourceReferences, locationNode); + graphBuilder.AddLink(node, CodeLinkCategories.SourceReferences, locationNode, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverriddenByGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverriddenByGraphQuery.cs index 980a3b701d722..a549654ec973a 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverriddenByGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverriddenByGraphQuery.cs @@ -21,15 +21,15 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol != null) { var overriddenMember = symbol.GetOverriddenMember(); if (overriddenMember != null) { var symbolNode = await graphBuilder.AddNodeAsync( - overriddenMember, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(node, RoslynGraphCategories.Overrides, symbolNode); + overriddenMember, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(node, RoslynGraphCategories.Overrides, symbolNode, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverridesGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverridesGraphQuery.cs index dd397440df7f6..e87423e4c3a0f 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverridesGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/OverridesGraphQuery.cs @@ -23,7 +23,7 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c foreach (var node in context.InputNodes) { - var symbol = graphBuilder.GetSymbol(node); + var symbol = graphBuilder.GetSymbol(node, cancellationToken); if (symbol is IMethodSymbol || symbol is IPropertySymbol || symbol is IEventSymbol) @@ -31,8 +31,8 @@ symbol is IPropertySymbol || var overrides = await SymbolFinder.FindOverridesAsync(symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); foreach (var o in overrides) { - var symbolNode = await graphBuilder.AddNodeAsync(o, relatedNode: node).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, RoslynGraphCategories.Overrides, node); + var symbolNode = await graphBuilder.AddNodeAsync(o, relatedNode: node, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(symbolNode, RoslynGraphCategories.Overrides, node, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs new file mode 100644 index 0000000000000..16bdf6b527190 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.GraphModel; +using Microsoft.CodeAnalysis.NavigateTo; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression +{ + internal sealed partial class SearchGraphQuery + { + private class ProgressionNavigateToSearchCallback : INavigateToSearchCallback + { + private readonly IGraphContext _context; + private readonly GraphBuilder _graphBuilder; + + public ProgressionNavigateToSearchCallback(IGraphContext context, GraphBuilder graphBuilder) + { + _context = context; + _graphBuilder = graphBuilder; + } + + public void Done(bool isFullyLoaded) + { + // Do nothing here. Even though the navigate to search completed, we still haven't passed any + // information along to progression. That will happen in GraphQueryManager.PopulateContextGraphAsync + } + + public void ReportProgress(int current, int maximum) + => _context.ReportProgress(current, maximum, null); + + public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + { + var node = await _graphBuilder.CreateNodeAsync(result, cancellationToken).ConfigureAwait(false); + if (node != null) + { + // _context.OutputNodes is not threadsafe. So ensure only one navto callback can mutate it at a time. + lock (this) + _context.OutputNodes.Add(node); + } + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs index 9839277a77d9f..2a525df7db468 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs @@ -2,31 +2,67 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.GraphModel; using Roslyn.Utilities; -using System.Runtime.ExceptionServices; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression { - internal sealed class SearchGraphQuery : IGraphQuery + internal sealed partial class SearchGraphQuery : IGraphQuery { + private readonly IThreadingContext _threadingContext; + private readonly IAsynchronousOperationListener _asyncListener; private readonly string _searchPattern; - public SearchGraphQuery(string searchPattern) - => _searchPattern = searchPattern; + public SearchGraphQuery( + string searchPattern, + IThreadingContext threadingContext, + IAsynchronousOperationListener asyncListener) + { + _threadingContext = threadingContext; + _asyncListener = asyncListener; + _searchPattern = searchPattern; + } + + public Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) + { + var option = solution.Options.GetOption(ProgressionOptions.SearchUsingNavigateToEngine); + return option + ? SearchUsingNavigateToEngineAsync(solution, context, cancellationToken) + : SearchUsingSymbolsAsync(solution, context, cancellationToken); + } + + private async Task SearchUsingNavigateToEngineAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) + { + var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); + var callback = new ProgressionNavigateToSearchCallback(context, graphBuilder); + var searcher = NavigateToSearcher.Create( + solution, + _asyncListener, + callback, + _searchPattern, + searchCurrentDocument: false, + NavigateToUtilities.GetKindsProvided(solution), + _threadingContext.DisposalToken); + + await searcher.SearchAsync(cancellationToken).ConfigureAwait(false); + + return graphBuilder; + } - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) + private async Task SearchUsingSymbolsAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) { var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); @@ -56,11 +92,13 @@ private async Task ProcessProjectAsync(Project project, GraphBuilder graphBuilde { await AddLinkedNodeForTypeAsync( project, namedType, graphBuilder, - symbol.DeclaringSyntaxReferences.Select(d => d.SyntaxTree)).ConfigureAwait(false); + symbol.DeclaringSyntaxReferences.Select(d => d.SyntaxTree), + cancellationToken).ConfigureAwait(false); } else { - await AddLinkedNodeForMemberAsync(project, symbol, graphBuilder).ConfigureAwait(false); + await AddLinkedNodeForMemberAsync( + project, symbol, graphBuilder, cancellationToken).ConfigureAwait(false); } } } @@ -68,30 +106,32 @@ await AddLinkedNodeForTypeAsync( } private async Task AddLinkedNodeForTypeAsync( - Project project, INamedTypeSymbol namedType, GraphBuilder graphBuilder, IEnumerable syntaxTrees) + Project project, INamedTypeSymbol namedType, GraphBuilder graphBuilder, + IEnumerable syntaxTrees, CancellationToken cancellationToken) { // If this named type is contained in a parent type, then just link farther up if (namedType.ContainingType != null) { var parentTypeNode = await AddLinkedNodeForTypeAsync( - project, namedType.ContainingType, graphBuilder, syntaxTrees).ConfigureAwait(false); - var typeNode = await graphBuilder.AddNodeAsync(namedType, relatedNode: parentTypeNode).ConfigureAwait(false); - graphBuilder.AddLink(parentTypeNode, GraphCommonSchema.Contains, typeNode); + project, namedType.ContainingType, graphBuilder, syntaxTrees, cancellationToken).ConfigureAwait(false); + var typeNode = await graphBuilder.AddNodeAsync(namedType, relatedNode: parentTypeNode, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(parentTypeNode, GraphCommonSchema.Contains, typeNode, cancellationToken); return typeNode; } else { // From here, we can link back up to the containing project item - var typeNode = await graphBuilder.AddNodeAsync(namedType, contextProject: project, contextDocument: null).ConfigureAwait(false); + var typeNode = await graphBuilder.AddNodeAsync( + namedType, contextProject: project, contextDocument: null, cancellationToken).ConfigureAwait(false); foreach (var tree in syntaxTrees) { var document = project.Solution.GetDocument(tree); Contract.ThrowIfNull(document); - var documentNode = graphBuilder.AddNodeForDocument(document); - graphBuilder.AddLink(documentNode, GraphCommonSchema.Contains, typeNode); + var documentNode = graphBuilder.AddNodeForDocument(document, cancellationToken); + graphBuilder.AddLink(documentNode, GraphCommonSchema.Contains, typeNode, cancellationToken); } return typeNode; @@ -99,7 +139,7 @@ private async Task AddLinkedNodeForTypeAsync( } private async Task AddLinkedNodeForMemberAsync( - Project project, ISymbol symbol, GraphBuilder graphBuilder) + Project project, ISymbol symbol, GraphBuilder graphBuilder, CancellationToken cancellationToken) { var member = symbol; Contract.ThrowIfNull(member.ContainingType); @@ -107,10 +147,10 @@ private async Task AddLinkedNodeForMemberAsync( var trees = member.DeclaringSyntaxReferences.Select(d => d.SyntaxTree); var parentTypeNode = await AddLinkedNodeForTypeAsync( - project, member.ContainingType, graphBuilder, trees).ConfigureAwait(false); + project, member.ContainingType, graphBuilder, trees, cancellationToken).ConfigureAwait(false); var memberNode = await graphBuilder.AddNodeAsync( - symbol, relatedNode: parentTypeNode).ConfigureAwait(false); - graphBuilder.AddLink(parentTypeNode, GraphCommonSchema.Contains, memberNode); + symbol, relatedNode: parentTypeNode, cancellationToken).ConfigureAwait(false); + graphBuilder.AddLink(parentTypeNode, GraphCommonSchema.Contains, memberNode, cancellationToken); return memberNode; } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueryManager.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueryManager.cs index 5ef2e76226cf9..496a36eba710b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueryManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueryManager.cs @@ -169,9 +169,9 @@ private static async Task PopulateContextGraphAsync(Solution solution, List SearchUsingNavigateToEngine = new( + nameof(ProgressionOptions), nameof(SearchUsingNavigateToEngine), defaultValue: false, + storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "SearchUsingNavigateToEngine")); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptionsProvider.cs b/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptionsProvider.cs new file mode 100644 index 0000000000000..51b4ba2457866 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptionsProvider.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression +{ + [ExportOptionProvider, Shared] + internal class ProgressionOptionsProvider : IOptionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ProgressionOptionsProvider() + { + } + + public ImmutableArray Options { get; } = ImmutableArray.Create( + ProgressionOptions.SearchUsingNavigateToEngine); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs index 6abc3be7a5918..110089eb3c1d3 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -234,7 +234,7 @@ private static void TryReportCompilationThrownAway(SolutionState solutionState, // We log the number of syntax trees that have been parsed even if there was no compilation created yet var projectState = solutionState.GetRequiredProjectState(projectId); var parsedTrees = 0; - foreach (var documentState in projectState.DocumentStates.States) + foreach (var (_, documentState) in projectState.DocumentStates.States) { if (documentState.TryGetSyntaxTree(out _)) { diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index cecf5cae341f3..5c8b43b3f2553 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -73,8 +73,8 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private ImmutableDictionary _projectToHierarchyMap = ImmutableDictionary.Empty; private ImmutableDictionary _projectToGuidMap = ImmutableDictionary.Empty; - private readonly Dictionary _projectToMaxSupportedLangVersionMap = new(); - private readonly Dictionary _projectToDependencyNodeTargetIdentifier = new(); + private ImmutableDictionary _projectToMaxSupportedLangVersionMap = ImmutableDictionary.Empty; + private ImmutableDictionary _projectToDependencyNodeTargetIdentifier = ImmutableDictionary.Empty; /// /// A map to fetch the path to a rule set file for a project. This right now is only used to implement @@ -101,6 +101,8 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] internal VisualStudioProjectTracker? _projectTracker; + private VirtualMemoryNotificationListener? _memoryListener; + private OpenFileTracker? _openFileTracker; internal FileChangeWatcher FileChangeWatcher { get; } internal FileWatchedPortableExecutableReferenceFactory FileWatchedReferenceFactory { get; } @@ -204,6 +206,14 @@ public async Task InitializeUIAffinitizedServicesAsync(IAsyncServiceProvider asy _openFileTracker = openFileTracker; } + var memoryListener = await VirtualMemoryNotificationListener.CreateAsync(this, _threadingContext, asyncServiceProvider, _threadingContext.DisposalToken).ConfigureAwait(true); + + // Update our fields first, so any asynchronous work that needs to use these is able to see the service. + lock (_gate) + { + _memoryListener = memoryListener; + } + openFileTracker.ProcessQueuedWorkOnUIThread(); } @@ -1305,10 +1315,9 @@ internal override Guid GetProjectGuid(ProjectId projectId) internal string? TryGetDependencyNodeTargetIdentifier(ProjectId projectId) { - lock (_gate) - { - return _projectToDependencyNodeTargetIdentifier.GetValueOrDefault(projectId, defaultValue: null); - } + // This doesn't take a lock since _projectToDependencyNodeTargetIdentifier is immutable + _projectToDependencyNodeTargetIdentifier.TryGetValue(projectId, out var identifier); + return identifier; } internal override void SetDocumentContext(DocumentId documentId) @@ -1589,8 +1598,10 @@ protected internal override void OnProjectRemoved(ProjectId projectId) _projectToHierarchyMap = _projectToHierarchyMap.Remove(projectId); _projectToGuidMap = _projectToGuidMap.Remove(projectId); - _projectToMaxSupportedLangVersionMap.Remove(projectId); - _projectToDependencyNodeTargetIdentifier.Remove(projectId); + // _projectToMaxSupportedLangVersionMap needs to be updated with ImmutableInterlocked since it can be mutated outside the lock + ImmutableInterlocked.TryRemove(ref _projectToMaxSupportedLangVersionMap, projectId, out _); + // _projectToDependencyNodeTargetIdentifier needs to be updated with ImmutableInterlocked since it can be mutated outside the lock + ImmutableInterlocked.TryRemove(ref _projectToDependencyNodeTargetIdentifier, projectId, out _); _projectToRuleSetFilePath.Remove(projectId); foreach (var (projectName, projects) in _projectSystemNameToProjectsMap) @@ -1986,20 +1997,22 @@ internal async Task EnsureDocumentOptionProvidersInitializedAsync(CancellationTo RegisterDocumentOptionProviders(_documentOptionsProviderFactories); } + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54137", AllowLocks = false)] internal void SetMaxLanguageVersion(ProjectId projectId, string? maxLanguageVersion) { - lock (_gate) - { - _projectToMaxSupportedLangVersionMap[projectId] = maxLanguageVersion; - } + ImmutableInterlocked.Update( + ref _projectToMaxSupportedLangVersionMap, + static (map, arg) => map.SetItem(arg.projectId, arg.maxLanguageVersion), + (projectId, maxLanguageVersion)); } + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54135", AllowLocks = false)] internal void SetDependencyNodeTargetIdentifier(ProjectId projectId, string targetIdentifier) { - lock (_gate) - { - _projectToDependencyNodeTargetIdentifier[projectId] = targetIdentifier; - } + ImmutableInterlocked.Update( + ref _projectToDependencyNodeTargetIdentifier, + static (map, arg) => map.SetItem(arg.projectId, arg.targetIdentifier), + (projectId, targetIdentifier)); } internal void RefreshProjectExistsUIContextForLanguage(string language) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs index 61b714b413b33..f29c8a9d05d17 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectTelemetry/VisualStudioProjectTelemetryService.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.ProjectTelemetry; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.Internal.VisualStudio.Shell; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Roslyn.Utilities; @@ -58,13 +59,15 @@ internal class VisualStudioProjectTelemetryService [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioProjectTelemetryService( VisualStudioWorkspaceImpl workspace, - IThreadingContext threadingContext) : base(threadingContext) + IThreadingContext threadingContext, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider) : base(threadingContext) { _workspace = workspace; _workQueue = new AsyncBatchingWorkQueue( TimeSpan.FromSeconds(1), NotifyTelemetryServiceAsync, + asynchronousOperationListenerProvider.GetListener(FeatureAttribute.Telemetry), threadingContext.DisposalToken); } @@ -117,7 +120,7 @@ private async Task StartWorkerAsync() cancellationToken).ConfigureAwait(false); } - private async Task NotifyTelemetryServiceAsync( + private async ValueTask NotifyTelemetryServiceAsync( ImmutableArray infos, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs index 5f5fda2abed13..02b2b54c05b97 100644 --- a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs @@ -739,6 +739,12 @@ private static async Task> GetReferencedSymbolsToLeftOfC var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var token = await semanticModel.SyntaxTree.GetTouchingWordAsync(caretPosition.Position, document.GetRequiredLanguageService(), cancellationToken).ConfigureAwait(false); + if (token.RawKind == 0) + { + // There is no touching word, so return empty immediately + return ImmutableArray.Empty; + } + var semanticInfo = semanticModel.GetSemanticInfo(token, document.Project.Solution.Workspace, cancellationToken); return semanticInfo.ReferencedSymbols; } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousDiagnosticListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousDiagnosticListTable.cs index d58222681a904..18e29d1645198 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousDiagnosticListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousDiagnosticListTable.cs @@ -34,8 +34,8 @@ private sealed class MiscellaneousDiagnosticListTable : VisualStudioBaseDiagnost { private readonly LiveTableDataSource _source; - public MiscellaneousDiagnosticListTable(Workspace workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider) : - base(workspace, provider) + public MiscellaneousDiagnosticListTable(Workspace workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider) + : base(workspace, provider) { _source = new LiveTableDataSource(workspace, diagnosticService, IdentifierString); diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousTodoListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousTodoListTable.cs index 8a92d5d82de14..6a431243b7fa2 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousTodoListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/MiscellaneousTodoListTable.cs @@ -31,8 +31,8 @@ public void StartListening(Workspace workspace, ITodoListProvider service) private sealed class MiscellaneousTodoListTable : VisualStudioBaseTodoListTable { - public MiscellaneousTodoListTable(Workspace workspace, ITodoListProvider todoListProvider, ITableManagerProvider provider) : - base(workspace, todoListProvider, IdentifierString, provider) + public MiscellaneousTodoListTable(Workspace workspace, ITodoListProvider todoListProvider, ITableManagerProvider provider) + : base(workspace, todoListProvider, IdentifierString, provider) { ConnectWorkspaceEvents(); } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs index 18186f1bfa44f..9ad21793b05f3 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs @@ -34,8 +34,8 @@ internal abstract partial class VisualStudioBaseDiagnosticListTable : AbstractTa StandardTableColumnDefinitions.SuppressionState }; - protected VisualStudioBaseDiagnosticListTable(Workspace workspace, ITableManagerProvider provider) : - base(workspace, provider, StandardTables.ErrorsTable) + protected VisualStudioBaseDiagnosticListTable(Workspace workspace, ITableManagerProvider provider) + : base(workspace, provider, StandardTables.ErrorsTable) { } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs index bb04f29635839..351cfcc868b05 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs @@ -126,8 +126,8 @@ private class TableEntriesSnapshot : AbstractTableEntriesSnapshot items) : - base(version, items, ImmutableArray.Empty) + DiagnosticTableEntriesSource source, int version, ImmutableArray items) + : base(version, items, ImmutableArray.Empty) { _source = source; } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.cs index 0401859b94c41..1ff234ca8b85c 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.cs @@ -79,8 +79,8 @@ public VisualStudioDiagnosticListTable( VisualStudioWorkspaceImpl workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider, - IErrorList errorList) : - base(workspace, provider) + IErrorList errorList) + : base(workspace, provider) { _errorList = errorList; @@ -104,15 +104,15 @@ private ITableDataSource GetCurrentDataSource() } /// this is for test only - internal VisualStudioDiagnosticListTable(Workspace workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider) : - base(workspace, provider) + internal VisualStudioDiagnosticListTable(Workspace workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider) + : base(workspace, provider) { AddInitialTableSource(workspace.CurrentSolution, new LiveTableDataSource(workspace, diagnosticService, IdentifierString)); } /// this is for test only - internal VisualStudioDiagnosticListTable(Workspace workspace, ExternalErrorDiagnosticUpdateSource errorSource, ITableManagerProvider provider) : - base(workspace, provider) + internal VisualStudioDiagnosticListTable(Workspace workspace, ExternalErrorDiagnosticUpdateSource errorSource, ITableManagerProvider provider) + : base(workspace, provider) { AddInitialTableSource(workspace.CurrentSolution, new BuildTableDataSource(workspace, errorSource)); } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioTodoListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioTodoListTable.cs index a6e98ac5c464f..850ca550b0157 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioTodoListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioTodoListTable.cs @@ -32,8 +32,8 @@ public void StartListening(Workspace workspace, ITodoListProvider service) internal class VisualStudioTodoListTable : VisualStudioBaseTodoListTable { // internal for testing - internal VisualStudioTodoListTable(Workspace workspace, ITodoListProvider todoListProvider, ITableManagerProvider provider) : - base(workspace, todoListProvider, IdentifierString, provider) + internal VisualStudioTodoListTable(Workspace workspace, ITodoListProvider todoListProvider, ITableManagerProvider provider) + : base(workspace, todoListProvider, IdentifierString, provider) { ConnectWorkspaceEvents(); } diff --git a/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs b/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs index 29c0c6b63ced8..4463117ab20ba 100644 --- a/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TodoComments/VisualStudioTodoCommentsService.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.TodoComments; using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api; @@ -38,9 +39,8 @@ internal class VisualStudioTodoCommentsService { private readonly VisualStudioWorkspaceImpl _workspace; private readonly EventListenerTracker _eventListenerTracker; - - private readonly ConcurrentDictionary> _documentToInfos - = new(); + private readonly IAsynchronousOperationListener _asyncListener; + private readonly ConcurrentDictionary> _documentToInfos = new(); /// /// Remote service connection. Created on demand when we startup and then @@ -61,11 +61,13 @@ private readonly TaskCompletionSource> eventListeners) : base(threadingContext) { _workspace = workspace; _eventListenerTracker = new EventListenerTracker(eventListeners, WellKnownEventListeners.TodoListProvider); + _asyncListener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.TodoCommentList); } public void Dispose() @@ -106,6 +108,7 @@ private async Task StartWorkerAsync() new AsyncBatchingWorkQueue( TimeSpan.FromSeconds(1), ProcessTodoCommentInfosAsync, + _asyncListener, cancellationToken)); var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); @@ -142,7 +145,7 @@ private void ComputeTodoCommentsInCurrentProcess(CancellationToken cancellationT workspaceKinds: WorkspaceKind.Host)); } - private Task ProcessTodoCommentInfosAsync( + private ValueTask ProcessTodoCommentInfosAsync( ImmutableArray docAndCommentsArray, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -181,7 +184,7 @@ private Task ProcessTodoCommentInfosAsync( } } - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; } private void AddFilteredInfos( diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs index ca4ee097e3db9..b7781005e2a44 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs @@ -123,8 +123,10 @@ public bool TryGetValue(string keyName, out object? content) content = ReferenceUpdate.ReferenceInfo.ReferenceType; break; case UnusedReferencesTableKeyNames.ReferenceName: - // It is unnecessary to display the full path to project and assembly files. - content = Path.GetFileNameWithoutExtension(ReferenceUpdate.ReferenceInfo.ItemSpecification); + // For Project and Assembly references, use the file name instead of overwhelming the user with the full path. + content = ReferenceUpdate.ReferenceInfo.ReferenceType != ReferenceType.Package + ? Path.GetFileName(ReferenceUpdate.ReferenceInfo.ItemSpecification) + : ReferenceUpdate.ReferenceInfo.ItemSpecification; break; case UnusedReferencesTableKeyNames.UpdateAction: content = ReferenceUpdate.Action; diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs index 4db7302726166..6dc53cda7f586 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs @@ -66,7 +66,8 @@ internal static ImmutableArray ReadReferences( } var autoReferences = projectAssets.Project?.Frameworks?.Values - .SelectMany(framework => framework.Dependencies?.Keys.Where(key => framework.Dependencies[key].AutoReferenced)) + .Where(framework => framework.Dependencies != null) + .SelectMany(framework => framework.Dependencies!.Keys.Where(key => framework.Dependencies[key].AutoReferenced)) .Distinct() .ToImmutableHashSet(); autoReferences ??= ImmutableHashSet.Empty; diff --git a/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs b/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs index 198cc448de094..9113ca6b9c011 100644 --- a/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs @@ -2,26 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Composition; using System.Runtime; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Options; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.LanguageServices.Implementation; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; -using Microsoft.VisualStudio.LanguageServices.Remote; -using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; namespace Microsoft.VisualStudio.LanguageServices { @@ -29,7 +25,6 @@ namespace Microsoft.VisualStudio.LanguageServices /// Listens to broadcast notifications from the Visual Studio Shell indicating that the application is running /// low on available virtual memory. /// - [Export, Shared] internal sealed class VirtualMemoryNotificationListener : ForegroundThreadAffinitizedObject, IVsBroadcastMessageEvents { // memory threshold to turn off full solution analysis - 200MB @@ -39,15 +34,13 @@ internal sealed class VirtualMemoryNotificationListener : ForegroundThreadAffini private const string LowVMMoreInfoLink = "https://go.microsoft.com/fwlink/?LinkID=799402&clcid=0x409"; private readonly VisualStudioWorkspace _workspace; - private readonly WorkspaceCacheService _workspaceCacheService; + private readonly WorkspaceCacheService? _workspaceCacheService; private bool _alreadyLogged; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VirtualMemoryNotificationListener( + private VirtualMemoryNotificationListener( IThreadingContext threadingContext, - SVsServiceProvider serviceProvider, + IVsShell shell, VisualStudioWorkspace workspace) : base(threadingContext, assertIsForeground: true) { @@ -63,11 +56,20 @@ public VirtualMemoryNotificationListener( _workspace.WorkspaceChanged += OnWorkspaceChanged; - var shell = (IVsShell)serviceProvider.GetService(typeof(SVsShell)); // Note: We never unhook this event sink. It lives for the lifetime of the host. ErrorHandler.ThrowOnFailure(shell.AdviseBroadcastMessages(this, out var cookie)); } + public static async Task CreateAsync(VisualStudioWorkspace workspace, IThreadingContext threadingContext, IAsyncServiceProvider serviceProvider, CancellationToken cancellationToken) + { + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var shell = (IVsShell?)await serviceProvider.GetServiceAsync(typeof(SVsShell)).ConfigureAwait(true); + Assumes.Present(shell); + + return new VirtualMemoryNotificationListener(threadingContext, shell, workspace); + } + /// /// Called by the Visual Studio Shell to notify components of a broadcast message. /// @@ -149,7 +151,7 @@ private void ShowInfoBarIfRequired() } // Show info bar. - _workspace.Services.GetService() + _workspace.Services.GetRequiredService() .ShowGlobalErrorInfo(ServicesVSResources.Visual_Studio_has_suspended_some_advanced_features_to_improve_performance, new InfoBarUI(ServicesVSResources.Re_enable, InfoBarUI.UIKind.Button, RenableBackgroundAnalysis), new InfoBarUI(ServicesVSResources.Learn_more, InfoBarUI.UIKind.HyperLink, diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index 2dad6684c85fe..25309ad0dfa83 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; @@ -191,7 +192,7 @@ void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuff openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, documentIdentity, _threadingContext); _openFiles.Add(moniker, openFile); - _threadingContext.JoinableTaskFactory.Run(() => openFile.RefreshFileAsync(CancellationToken.None)); + _threadingContext.JoinableTaskFactory.Run(() => openFile.RefreshFileAsync(CancellationToken.None).AsTask()); // Update the RDT flags to ensure the file can't be saved or appears in any MRUs as it's a temporary generated file name. var cookie = ((IVsRunningDocumentTable4)_runningDocumentTable).GetDocumentCookie(moniker); @@ -247,7 +248,7 @@ private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisp /// /// A queue used to batch updates to the file. /// - private readonly AsyncBatchingDelay _batchingWorkQueue; + private readonly AsyncBatchingWorkQueue _batchingWorkQueue; /// /// The of the active window. This may be null if we're in the middle of construction and @@ -284,7 +285,7 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff _workspace.WorkspaceChanged += OnWorkspaceChanged; - _batchingWorkQueue = new AsyncBatchingDelay( + _batchingWorkQueue = new AsyncBatchingWorkQueue( TimeSpan.FromSeconds(1), RefreshFileAsync, asyncListener: _fileManager._listener, @@ -321,7 +322,7 @@ public void Dispose() private string GeneratorDisplayName => _documentIdentity.GeneratorTypeName; - public async Task RefreshFileAsync(CancellationToken cancellationToken) + public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) { SourceText? generatedSource = null; var project = _workspace.CurrentSolution.GetProject(_documentIdentity.DocumentId.ProjectId); @@ -436,7 +437,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) if (await oldProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false) != await newProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false)) { - _batchingWorkQueue.RequeueWork(); + _batchingWorkQueue.AddWork(); } }, _cancellationTokenSource.Token).CompletesAsyncOperation(asyncToken); } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs index 23b3b6b1a8f8e..3d9a0808a469d 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces [Shared] internal partial class VisualStudioProjectCacheHostServiceFactory : IWorkspaceServiceFactory { - private const int ImplicitCacheTimeoutInMS = 10000; + private static readonly TimeSpan ImplicitCacheTimeout = TimeSpan.FromMilliseconds(10000); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -42,7 +42,7 @@ private static IWorkspaceService GetMiscProjectCache(HostWorkspaceServices works return new ProjectCacheService(workspaceServices.Workspace); } - var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeout); // Also clear the cache when the solution is cleared or removed. workspaceServices.Workspace.WorkspaceChanged += (s, e) => @@ -59,7 +59,7 @@ private static IWorkspaceService GetMiscProjectCache(HostWorkspaceServices works private static IWorkspaceService GetVisualStudioProjectCache(HostWorkspaceServices workspaceServices) { // We will finish setting this up in VisualStudioWorkspaceImpl.DeferredInitializationState - return new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + return new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeout); } internal static void ConnectProjectCacheServiceToDocumentTracking(HostWorkspaceServices workspaceServices, ProjectCacheService projectCacheService) diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 324c659f597a5..c61ad491ee904 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -72,7 +72,9 @@ + + diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index cf0c02e3f8465..423f550810185 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -25,6 +25,12 @@ "Title"="Enable experimental C#/VB LSP completion experience" "PreviewPaneChannels"="IntPreview,int.main" +// The option page configuration is duplicated in RoslynPackage +// [ProvideToolWindow(typeof(ValueTracking.ValueTrackingToolWindow))] +[$RootKey$\ToolWindows\{60a19d42-2dd7-43f3-be90-c7a9cb7d28f4}] +"Name"="Microsoft.VisualStudio.LanguageServices.ValueTracking.ValueTrackingToolWindow" +@="{6cf2e545-6109-4730-8883-cf43d7aec3e1}" + // 68b46364-d378-42f2-9e72-37d86c5f4468 is the guid for SettingsEditorFactory // 6cf2e545-6109-4730-8883-cf43d7aec3e1 is the guid for RoslynPackage [$RootKey$\Editors\{68b46364-d378-42f2-9e72-37d86c5f4468}] diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index bfa8dd1049081..0a8892990afed 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -440,7 +440,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) _workQueue.AddWork((solutionChanged, changedProject)); } - private Task ProcessWorkQueueAsync( + private ValueTask ProcessWorkQueueAsync( ImmutableArray<(bool solutionChanged, ProjectId? changedProject)> workQueue, CancellationToken cancellationToken) { ThisCanBeCalledOnAnyThread(); @@ -449,13 +449,13 @@ private Task ProcessWorkQueueAsync( // If we've been disconnected, then there's no point proceeding. if (_workspace == null || !IsEnabled) - return Task.CompletedTask; + return ValueTaskFactory.CompletedTask; return ProcessWorkQueueWorkerAsync(workQueue, cancellationToken); } [MethodImpl(MethodImplOptions.NoInlining)] - private async Task ProcessWorkQueueWorkerAsync( + private async ValueTask ProcessWorkQueueWorkerAsync( ImmutableArray<(bool solutionChanged, ProjectId? changedProject)> workQueue, CancellationToken cancellationToken) { ThisCanBeCalledOnAnyThread(); diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 3e01cafb3530a..c28ba4669a1bb 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -20,28 +20,23 @@ using Microsoft.CodeAnalysis.Logging; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.ColorSchemes; using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings; using Microsoft.VisualStudio.LanguageServices.Experimentation; using Microsoft.VisualStudio.LanguageServices.Implementation; -using Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute; using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.Interactive; using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.RuleSets; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetry; using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; -using Microsoft.VisualStudio.LanguageServices.Implementation.TodoComments; using Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences; using Microsoft.VisualStudio.LanguageServices.Telemetry; using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Shell.ServiceBroker; using Microsoft.VisualStudio.TaskStatusCenter; using Microsoft.VisualStudio.Telemetry; using Microsoft.VisualStudio.TextManager.Interop; @@ -52,6 +47,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Setup { [Guid(Guids.RoslynPackageIdString)] + + // The option page configuration is duplicated in PackageRegistration.pkgdef + [ProvideToolWindow(typeof(ValueTracking.ValueTrackingToolWindow))] internal sealed class RoslynPackage : AbstractPackage { // The randomly-generated key name is used for serializing the ILSpy decompiler EULA preference to the .SUO @@ -186,7 +184,6 @@ protected override async Task LoadComponentsAsync(CancellationToken cancellation this.ComponentModel.GetService().Initialize(this); this.ComponentModel.GetService(); - this.ComponentModel.GetService(); // The misc files workspace needs to be loaded on the UI thread. This way it will have // the appropriate task scheduler to report events on. @@ -204,6 +201,19 @@ protected override async Task LoadComponentsAsync(CancellationToken cancellation LoadComponentsBackgroundAsync(cancellationToken).Forget(); } + // Overrides for VSSDK003 fix + // See https://github.com/Microsoft/VSSDK-Analyzers/blob/main/doc/VSSDK003.md + public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType) + => toolWindowType == typeof(ValueTracking.ValueTrackingToolWindow).GUID + ? this + : base.GetAsyncToolWindowFactory(toolWindowType); + + protected override string GetToolWindowTitle(Type toolWindowType, int id) + => base.GetToolWindowTitle(toolWindowType, id); + + protected override Task InitializeToolWindowAsync(Type toolWindowType, int id, CancellationToken cancellationToken) + => Task.FromResult((object?)null); + private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationToken) { await TaskScheduler.Default; diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index cf374e9343835..caabce2d12c40 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1656,6 +1656,9 @@ I agree to all of the foregoing: Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + + Skip analyzers for implicitly triggered builds + This action cannot be undone. Do you wish to continue? @@ -1747,4 +1750,27 @@ I agree to all of the foregoing: Overridden members + + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + + + Calculating... + Used in UI to represent progress in the context of loading items. + + + Derived types + + + Implemented interfaces + + + Implementing types + + + Inherited interfaces + + + Select an appropriate symbol to start value tracking + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs b/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs index b8017f09fba9a..e31883c4499a7 100644 --- a/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs +++ b/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs @@ -4,11 +4,9 @@ #nullable disable -using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.VisualStudio.Telemetry; using Roslyn.Utilities; @@ -31,7 +29,7 @@ public bool IsEnabled(FunctionId functionId) public void Log(FunctionId functionId, LogMessage logMessage) { - if (logMessage.LogLevel < LogLevel.Information) + if (IgnoreMessage(logMessage)) { return; } @@ -55,7 +53,7 @@ public void Log(FunctionId functionId, LogMessage logMessage) public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int blockId, CancellationToken cancellationToken) { - if (!(logMessage is KeyValueLogMessage kvLogMessage)) + if (IgnoreMessage(logMessage)) { return; } @@ -63,7 +61,9 @@ public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int bloc try { // guard us from exception thrown by telemetry - _pendingScopes[blockId] = CreateAndStartScope(kvLogMessage.Kind, functionId); + var kind = GetKind(logMessage); + + _pendingScopes[blockId] = CreateAndStartScope(kind, functionId); } catch { @@ -72,7 +72,7 @@ public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int bloc public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int blockId, int delta, CancellationToken cancellationToken) { - if (!(logMessage is KeyValueLogMessage kvLogMessage)) + if (IgnoreMessage(logMessage)) { return; } @@ -80,14 +80,15 @@ public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int blockI try { // guard us from exception thrown by telemetry - var kind = kvLogMessage.Kind; + var kind = GetKind(logMessage); + switch (kind) { case LogType.Trace: - EndScope(functionId, blockId, kvLogMessage, cancellationToken); + EndScope(functionId, blockId, logMessage, cancellationToken); return; case LogType.UserAction: - EndScope(functionId, blockId, kvLogMessage, cancellationToken); + EndScope(functionId, blockId, logMessage, cancellationToken); return; default: throw ExceptionUtilities.UnexpectedValue(kind); @@ -98,7 +99,19 @@ public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int blockI } } - private void EndScope(FunctionId functionId, int blockId, KeyValueLogMessage kvLogMessage, CancellationToken cancellationToken) + private static bool IgnoreMessage(LogMessage logMessage) + => logMessage.LogLevel < LogLevel.Information; + + private static LogType GetKind(LogMessage logMessage) + => logMessage is KeyValueLogMessage kvLogMessage + ? kvLogMessage.Kind + : logMessage.LogLevel switch + { + >= LogLevel.Information => LogType.UserAction, + _ => LogType.Trace + }; + + private void EndScope(FunctionId functionId, int blockId, LogMessage logMessage, CancellationToken cancellationToken) where T : OperationEvent { if (!_pendingScopes.TryRemove(blockId, out var value)) @@ -109,7 +122,7 @@ private void EndScope(FunctionId functionId, int blockId, KeyValueLogMessage var operation = (TelemetryScope)value; - AppendProperties(operation.EndEvent, functionId, kvLogMessage); + UpdateEvent(operation.EndEvent, functionId, logMessage); operation.End(cancellationToken.IsCancellationRequested ? TelemetryResult.UserCancel : TelemetryResult.Success); } @@ -132,10 +145,24 @@ private static TelemetryEvent CreateTelemetryEvent(FunctionId functionId, LogMes var eventName = functionId.GetEventName(); var telemetryEvent = new TelemetryEvent(eventName); + return UpdateEvent(telemetryEvent, functionId, logMessage); + } + + private static TelemetryEvent UpdateEvent(TelemetryEvent telemetryEvent, FunctionId functionId, LogMessage logMessage) + { if (logMessage is KeyValueLogMessage kvLogMessage) { telemetryEvent = AppendProperties(telemetryEvent, functionId, kvLogMessage); } + else + { + var message = logMessage.GetMessage(); + if (!string.IsNullOrWhiteSpace(message)) + { + var propertyName = functionId.GetPropertyName("Message"); + telemetryEvent.Properties.Add(propertyName, message); + } + } return telemetryEvent; } diff --git a/src/VisualStudio/Core/Def/ValueTracking/BindableTextBlock.cs b/src/VisualStudio/Core/Def/ValueTracking/BindableTextBlock.cs new file mode 100644 index 0000000000000..37f1696f34b32 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/BindableTextBlock.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class BindableTextBlock : TextBlock + { + public IList InlineCollection + { + get { return (ObservableCollection)GetValue(InlineListProperty); } + set { SetValue(InlineListProperty, value); } + } + + public static readonly DependencyProperty InlineListProperty = + DependencyProperty.Register(nameof(InlineCollection), typeof(IList), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged)); + + private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + var textBlock = (BindableTextBlock)sender; + var newList = (IList)e.NewValue; + + textBlock.Inlines.Clear(); + foreach (var inline in newList) + { + textBlock.Inlines.Add(inline); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ComputingTreeViewItem.cs b/src/VisualStudio/Core/Def/ValueTracking/ComputingTreeViewItem.cs new file mode 100644 index 0000000000000..0fcf993b01616 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ComputingTreeViewItem.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Windows.Threading; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class ComputingTreeViewItem : TreeViewItemBase + { + public string Text => ServicesVSResources.Calculating; + + public ComputingTreeViewItem() + { + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/EmptyTreeViewItem.cs b/src/VisualStudio/Core/Def/ValueTracking/EmptyTreeViewItem.cs new file mode 100644 index 0000000000000..767ecab7af32f --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/EmptyTreeViewItem.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class EmptyTreeViewItem : TreeViewItemBase + { + public static EmptyTreeViewItem Instance { get; } = new(); + + private EmptyTreeViewItem() + { + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs new file mode 100644 index 0000000000000..4b4267b6988fe --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Windows.Documents; +using System.Windows.Media; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class TreeItemViewModel : TreeViewItemBase + { + private readonly SourceText _sourceText; + private readonly Glyph _glyph; + private readonly IGlyphService _glyphService; + + protected ValueTrackingTreeViewModel TreeViewModel { get; } + protected TextSpan TextSpan { get; } + protected LineSpan LineSpan { get; } + protected IThreadingContext ThreadingContext { get; } + protected DocumentId DocumentId { get; } + protected Workspace Workspace { get; } + + public int LineNumber => LineSpan.Start + 1; // LineSpan is 0 indexed, editors are not + + public string FileName { get; } + + public ImageSource GlyphImage => _glyph.GetImageSource(_glyphService); + public bool ShowGlyph => !IsLoading; + + public ImmutableArray ClassifiedSpans { get; } + + public ImmutableArray Inlines => CalculateInlines(); + + public TreeItemViewModel( + TextSpan textSpan, + SourceText sourceText, + DocumentId documentId, + string fileName, + Glyph glyph, + ImmutableArray classifiedSpans, + ValueTrackingTreeViewModel treeViewModel, + IGlyphService glyphService, + IThreadingContext threadingContext, + Workspace workspace, + ImmutableArray children = default) + : base() + { + FileName = fileName; + TextSpan = textSpan; + _sourceText = sourceText; + ClassifiedSpans = classifiedSpans; + TreeViewModel = treeViewModel; + ThreadingContext = threadingContext; + + _glyph = glyph; + _glyphService = glyphService; + Workspace = workspace; + DocumentId = documentId; + + if (!children.IsDefaultOrEmpty) + { + foreach (var child in children) + { + ChildItems.Add(child); + } + } + + sourceText.GetLineAndOffset(textSpan.Start, out var lineStart, out var _); + sourceText.GetLineAndOffset(textSpan.End, out var lineEnd, out var _); + LineSpan = LineSpan.FromBounds(lineStart, lineEnd); + + PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(IsLoading)) + { + NotifyPropertyChanged(nameof(ShowGlyph)); + } + }; + } + + public virtual void NavigateTo() + { + var navigationService = Workspace.Services.GetService(); + if (navigationService is null) + { + return; + } + + // While navigating do not activate the tab, which will change focus from the tool window + var options = Workspace.Options + .WithChangedOption(new OptionKey(NavigationOptions.PreferProvisionalTab), true) + .WithChangedOption(new OptionKey(NavigationOptions.ActivateTab), false); + + navigationService.TryNavigateToLineAndOffset(Workspace, DocumentId, LineSpan.Start, 0, options, ThreadingContext.DisposalToken); + } + + private ImmutableArray CalculateInlines() + { + if (ClassifiedSpans.IsDefaultOrEmpty) + { + return ImmutableArray.Empty; + } + + var classifiedTexts = ClassifiedSpans.SelectAsArray( + cs => + { + return new ClassifiedText(cs.ClassificationType, _sourceText.ToString(cs.TextSpan)); + }); + + var spanStartPosition = TextSpan.Start - ClassifiedSpans[0].TextSpan.Start; + var spanEndPosition = TextSpan.End - ClassifiedSpans[0].TextSpan.End; + + return classifiedTexts.ToInlines( + TreeViewModel.ClassificationFormatMap, + TreeViewModel.ClassificationTypeMap, + (run, classifiedText, position) => + { + if (TreeViewModel.HighlightBrush is not null) + { + if (position >= spanStartPosition && position <= spanEndPosition) + { + run.SetValue( + TextElement.BackgroundProperty, + TreeViewModel.HighlightBrush); + } + } + }).ToImmutableArray(); + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/TreeViewItemBase.cs b/src/VisualStudio/Core/Def/ValueTracking/TreeViewItemBase.cs new file mode 100644 index 0000000000000..655dd6e2bef15 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/TreeViewItemBase.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class TreeViewItemBase : INotifyPropertyChanged + { + public ObservableCollection ChildItems { get; } = new(); + public TreeViewItemBase? Parent { get; set; } + + private bool _isExpanded = false; + public virtual bool IsNodeExpanded + { + get => _isExpanded; + set => SetProperty(ref _isExpanded, value); + } + + private bool _isSelected = false; + public bool IsNodeSelected + { + get => _isSelected; + set => SetProperty(ref _isSelected, value); + } + + private bool _isLoading; + public bool IsLoading + { + get => _isLoading; + set => SetProperty(ref _isLoading, value); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + public TreeViewItemBase() + { + ChildItems.CollectionChanged += ChildItems_CollectionChanged; + } + + /// + /// Returns the next logical item in the tree that could be seen (Parent is expanded) + /// + public TreeViewItemBase GetNextInTree() + { + if (IsNodeExpanded && ChildItems.Any()) + { + return ChildItems.First(); + } + + var sibling = GetSibling(next: true); + if (sibling is not null) + { + return sibling; + } + + return Parent?.GetSibling(next: true) ?? this; + } + + /// + /// Returns the previous logical item in the tree that could be seen (Parent is expanded) + /// + public TreeViewItemBase GetPreviousInTree() + { + var sibling = GetSibling(next: false); + if (sibling is not null) + { + return sibling.GetLastVisibleDescendentOrSelf(); + } + + return Parent ?? this; + } + + private TreeViewItemBase GetLastVisibleDescendentOrSelf() + { + if (!IsNodeExpanded || ChildItems.Count == 0) + { + return this; + } + + var lastChild = ChildItems.Last(); + return lastChild.GetLastVisibleDescendentOrSelf(); + } + + private TreeViewItemBase? GetSibling(bool next = true) + { + if (Parent is null) + { + return null; + } + + var thisIndex = Parent.ChildItems.IndexOf(this); + var siblingIndex = next ? thisIndex + 1 : thisIndex - 1; + + if (siblingIndex < 0 || siblingIndex >= Parent.ChildItems.Count) + { + return null; + } + + return Parent.ChildItems[siblingIndex]; + } + + private void ChildItems_CollectionChanged(object _, NotifyCollectionChangedEventArgs args) + { + if (args.Action is not NotifyCollectionChangedAction.Add and not NotifyCollectionChangedAction.Remove) + { + return; + } + + SetParents(args.OldItems, null); + SetParents(args.NewItems, this); + + static void SetParents(IList? items, TreeViewItemBase? parent) + { + if (items is null) + { + return; + } + + foreach (var item in items.Cast()) + { + item.Parent = parent; + } + } + } + + protected void SetProperty(ref T field, T value, [CallerMemberName] string name = "") + { + if (EqualityComparer.Default.Equals(field, value)) + { + return; + } + + field = value; + NotifyPropertyChanged(name); + } + + protected void NotifyPropertyChanged(string name) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs new file mode 100644 index 0000000000000..a11aefa1f07d0 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.ValueTracking; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class ValueTrackedTreeItemViewModel : TreeItemViewModel + { + private bool _childrenCalculated; + private readonly Solution _solution; + private readonly IGlyphService _glyphService; + private readonly IValueTrackingService _valueTrackingService; + private readonly ValueTrackedItem _trackedItem; + + public override bool IsNodeExpanded + { + get => base.IsNodeExpanded; + set + { + base.IsNodeExpanded = value; + CalculateChildren(); + } + } + + public ValueTrackedTreeItemViewModel( + ValueTrackedItem trackedItem, + Solution solution, + ValueTrackingTreeViewModel treeViewModel, + IGlyphService glyphService, + IValueTrackingService valueTrackingService, + IThreadingContext threadingContext, + string fileName, + ImmutableArray children = default) + : base( + trackedItem.Span, + trackedItem.SourceText, + trackedItem.DocumentId, + fileName, + trackedItem.Glyph, + trackedItem.ClassifiedSpans, + treeViewModel, + glyphService, + threadingContext, + solution.Workspace, + children: children) + { + + _trackedItem = trackedItem; + _solution = solution; + _glyphService = glyphService; + _valueTrackingService = valueTrackingService; + + if (children.IsDefaultOrEmpty) + { + // Add an empty item so the treeview has an expansion showing to calculate + // the actual children of the node + ChildItems.Add(EmptyTreeViewItem.Instance); + + ChildItems.CollectionChanged += (s, a) => + { + NotifyPropertyChanged(nameof(ChildItems)); + }; + } + } + + private void CalculateChildren() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_childrenCalculated || IsLoading) + { + return; + } + + TreeViewModel.LoadingCount++; + IsLoading = true; + ChildItems.Clear(); + + var computingItem = new ComputingTreeViewItem(); + ChildItems.Add(computingItem); + + System.Threading.Tasks.Task.Run(async () => + { + try + { + var children = await CalculateChildrenAsync(ThreadingContext.DisposalToken).ConfigureAwait(false); + + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + ChildItems.Clear(); + + foreach (var child in children) + { + ChildItems.Add(child); + } + } + finally + { + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + TreeViewModel.LoadingCount--; + _childrenCalculated = true; + IsLoading = false; + } + }, ThreadingContext.DisposalToken); + } + + public override void NavigateTo() + { + var navigationService = Workspace.Services.GetService(); + if (navigationService is null) + { + return; + } + + // While navigating do not activate the tab, which will change focus from the tool window + var options = Workspace.Options + .WithChangedOption(new OptionKey(NavigationOptions.PreferProvisionalTab), true) + .WithChangedOption(new OptionKey(NavigationOptions.ActivateTab), false); + + navigationService.TryNavigateToSpan(Workspace, DocumentId, _trackedItem.Span, options, ThreadingContext.DisposalToken); + } + + private async Task> CalculateChildrenAsync(CancellationToken cancellationToken) + { + var valueTrackedItems = await _valueTrackingService.TrackValueSourceAsync( + _solution, + _trackedItem, + cancellationToken).ConfigureAwait(false); + + var builder = ImmutableArray.CreateBuilder(valueTrackedItems.Length); + + foreach (var valueTrackedItem in valueTrackedItems) + { + var document = _solution.GetRequiredDocument(valueTrackedItem.DocumentId); + var fileName = document.FilePath ?? document.Name; + + builder.Add(new ValueTrackedTreeItemViewModel( + valueTrackedItem, + _solution, + TreeViewModel, + _glyphService, + _valueTrackingService, + ThreadingContext, + fileName + )); + } + + return builder.ToImmutableArray(); + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs new file mode 100644 index 0000000000000..58953daae4ea4 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs @@ -0,0 +1,271 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.ValueTracking; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.LanguageServices.Setup; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; +using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + [Export(typeof(ICommandHandler))] + [ContentType(ContentTypeNames.RoslynContentType)] + [Name(PredefinedCommandHandlerNames.ShowValueTracking)] + internal class ValueTrackingCommandHandler : ICommandHandler + { + private readonly IAsyncServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + private readonly ClassificationTypeMap _typeMap; + private readonly IClassificationFormatMapService _classificationFormatMapService; + private readonly IGlyphService _glyphService; + private readonly IEditorFormatMapService _formatMapService; + private RoslynPackage? _roslynPackage; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ValueTrackingCommandHandler( + SVsServiceProvider serviceProvider, + IThreadingContext threadingContext, + ClassificationTypeMap typeMap, + IClassificationFormatMapService classificationFormatMapService, + IGlyphService glyphService, + IEditorFormatMapService formatMapService) + { + _serviceProvider = (IAsyncServiceProvider)serviceProvider; + _threadingContext = threadingContext; + _typeMap = typeMap; + _classificationFormatMapService = classificationFormatMapService; + _glyphService = glyphService; + _formatMapService = formatMapService; + } + + public string DisplayName => "Go to value tracking"; + + public CommandState GetCommandState(ValueTrackingEditorCommandArgs args) + => CommandState.Available; + + public bool ExecuteCommand(ValueTrackingEditorCommandArgs args, CommandExecutionContext executionContext) + { + using var logger = Logger.LogBlock(FunctionId.ValueTracking_Command, CancellationToken.None, LogLevel.Information); + + var cancellationToken = executionContext.OperationContext.UserCancellationToken; + var caretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer); + if (!caretPosition.HasValue) + { + return false; + } + + var textSpan = new TextSpan(caretPosition.Value.Position, 0); + var sourceTextContainer = args.SubjectBuffer.AsTextContainer(); + var document = sourceTextContainer.GetOpenDocumentInCurrentContext(); + if (document is null) + { + return false; + } + + _threadingContext.JoinableTaskFactory.RunAsync(async () => + { + var selectedSymbol = await GetSelectedSymbolAsync(textSpan, document, cancellationToken).ConfigureAwait(false); + if (selectedSymbol is null) + { + // TODO: Show error dialog + return; + } + + var syntaxTree = document.GetRequiredSyntaxTreeSynchronously(cancellationToken); + var location = Location.Create(syntaxTree, textSpan); + + await ShowToolWindowAsync(args.TextView, selectedSymbol, location, document.Project.Solution, cancellationToken).ConfigureAwait(false); + }); + + return true; + } + + private static async Task GetSelectedSymbolAsync(TextSpan textSpan, Document document, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var selectedNode = root.FindNode(textSpan); + if (selectedNode is null) + { + return null; + } + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var selectedSymbol = + semanticModel.GetSymbolInfo(selectedNode, cancellationToken).Symbol + ?? semanticModel.GetDeclaredSymbol(selectedNode, cancellationToken); + + if (selectedSymbol is null) + { + return null; + } + + return selectedSymbol switch + { + ILocalSymbol + or IPropertySymbol { SetMethod: not null } + or IFieldSymbol { IsReadOnly: false } + or IEventSymbol + or IParameterSymbol + => selectedSymbol, + + _ => null + }; + } + + private async Task ShowToolWindowAsync(ITextView textView, ISymbol selectedSymbol, Location location, Solution solution, CancellationToken cancellationToken) + { + var item = await ValueTrackedItem.TryCreateAsync(solution, location, selectedSymbol, cancellationToken: cancellationToken).ConfigureAwait(false); + if (item is null) + { + return; + } + + var toolWindow = await GetOrCreateToolWindowAsync(textView, cancellationToken).ConfigureAwait(false); + if (toolWindow?.ViewModel is null) + { + return; + } + + var valueTrackingService = solution.Workspace.Services.GetRequiredService(); + var classificationFormatMap = _classificationFormatMapService.GetClassificationFormatMap(textView); + + var childItems = await valueTrackingService.TrackValueSourceAsync(solution, item, cancellationToken).ConfigureAwait(false); + var childViewModels = childItems.SelectAsArray(child => CreateViewModel(child)); + + RoslynDebug.AssertNotNull(location.SourceTree); + var document = solution.GetRequiredDocument(location.SourceTree); + + var sourceText = await location.SourceTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync(document, location.SourceSpan, cancellationToken).ConfigureAwait(false); + var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, cancellationToken).ConfigureAwait(false); + + var root = new TreeItemViewModel( + location.SourceSpan, + sourceText, + document.Id, + document.FilePath ?? document.Name, + selectedSymbol.GetGlyph(), + classificationResult.ClassifiedSpans, + toolWindow.ViewModel, + _glyphService, + _threadingContext, + solution.Workspace, + children: childViewModels); + + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + toolWindow.ViewModel.Roots.Clear(); + toolWindow.ViewModel.Roots.Add(root); + + await ShowToolWindowAsync(cancellationToken).ConfigureAwait(true); + + TreeItemViewModel CreateViewModel(ValueTrackedItem valueTrackedItem, ImmutableArray children = default) + { + var document = solution.GetRequiredDocument(valueTrackedItem.DocumentId); + var fileName = document.FilePath ?? document.Name; + + return new ValueTrackedTreeItemViewModel( + valueTrackedItem, + solution, + toolWindow.ViewModel, + _glyphService, + valueTrackingService, + _threadingContext, + fileName, + children); + } + } + + private async Task ShowToolWindowAsync(CancellationToken cancellationToken) + { + var roslynPackage = await TryGetRoslynPackageAsync(cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(roslynPackage); + + await roslynPackage.ShowToolWindowAsync( + typeof(ValueTrackingToolWindow), + 0, + true, + roslynPackage.DisposalToken).ConfigureAwait(false); + } + + private async Task GetOrCreateToolWindowAsync(ITextView textView, CancellationToken cancellationToken) + { + var roslynPackage = await TryGetRoslynPackageAsync(cancellationToken).ConfigureAwait(false); + if (roslynPackage is null) + { + return null; + } + + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + if (ValueTrackingToolWindow.Instance is null) + { + var factory = roslynPackage.GetAsyncToolWindowFactory(Guids.ValueTrackingToolWindowId); + + var viewModel = new ValueTrackingTreeViewModel(_classificationFormatMapService.GetClassificationFormatMap(textView), _typeMap, _formatMapService); + + factory.CreateToolWindow(Guids.ValueTrackingToolWindowId, 0, viewModel); + await factory.InitializeToolWindowAsync(Guids.ValueTrackingToolWindowId, 0); + + // FindWindowPaneAsync creates an instance if it does not exist + ValueTrackingToolWindow.Instance = (ValueTrackingToolWindow)await roslynPackage.FindWindowPaneAsync( + typeof(ValueTrackingToolWindow), + 0, + true, + roslynPackage.DisposalToken).ConfigureAwait(false); + } + + // This can happen if the tool window was initialized outside of this command handler. The ViewModel + // still needs to be initialized but had no necessary context. Provide that context now in the command handler. + if (ValueTrackingToolWindow.Instance.ViewModel is null) + { + ValueTrackingToolWindow.Instance.ViewModel = new ValueTrackingTreeViewModel(_classificationFormatMapService.GetClassificationFormatMap(textView), _typeMap, _formatMapService); + } + + return ValueTrackingToolWindow.Instance; + } + + private async ValueTask TryGetRoslynPackageAsync(CancellationToken cancellationToken) + { + if (_roslynPackage is null) + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var shell = (IVsShell7?)await _serviceProvider.GetServiceAsync(typeof(SVsShell)).ConfigureAwait(true); + Assumes.Present(shell); + await shell.LoadPackageAsync(typeof(RoslynPackage).GUID); + + if (ErrorHandler.Succeeded(((IVsShell)shell).IsPackageLoaded(typeof(RoslynPackage).GUID, out var package))) + { + _roslynPackage = (RoslynPackage)package; + } + } + + return _roslynPackage; + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingEditorCommandArgs.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingEditorCommandArgs.cs new file mode 100644 index 0000000000000..13c4e55145669 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingEditorCommandArgs.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Editor.Commanding; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class ValueTrackingEditorCommandArgs : EditorCommandArgs + { + public ValueTrackingEditorCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs new file mode 100644 index 0000000000000..c47b9d092cc54 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Windows.Controls; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Shell; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + [Guid(Guids.ValueTrackingToolWindowIdString)] + internal class ValueTrackingToolWindow : ToolWindowPane + { + private readonly Grid _rootGrid = new(); + + public static ValueTrackingToolWindow? Instance { get; set; } + + private ValueTrackingTreeViewModel? _viewModel; + public ValueTrackingTreeViewModel? ViewModel + { + get => _viewModel; + set + { + if (value is null) + { + throw new ArgumentNullException(nameof(ViewModel)); + } + + _viewModel = value; + _rootGrid.Children.Clear(); + _rootGrid.Children.Add(new ValueTrackingTree(_viewModel)); + } + } + + /// + /// This paramterless constructor is used when + /// the tool window is initialized on open without any + /// context. If the tool window is left open across shutdown/restart + /// of VS for example, then this gets called. + /// + public ValueTrackingToolWindow() : base(null) + { + Caption = ServicesVSResources.Value_Tracking; + + _rootGrid.Children.Add(new TextBlock() + { + Text = ServicesVSResources.Select_an_appropriate_symbol_to_start_value_tracking + }); + + Content = _rootGrid; + } + + public ValueTrackingToolWindow(ValueTrackingTreeViewModel viewModel) + : base(null) + { + Caption = ServicesVSResources.Value_Tracking; + Content = _rootGrid; + ViewModel = viewModel; + } + + public TreeItemViewModel? Root + { + get => ViewModel?.Roots.Single(); + set + { + if (value is null) + { + return; + } + + Contract.ThrowIfNull(ViewModel); + + ViewModel.Roots.Clear(); + ViewModel.Roots.Add(value); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml new file mode 100644 index 0000000000000..88087b3356b33 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs new file mode 100644 index 0000000000000..4b35d9a0e61b9 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + /// + /// Interaction logic for ValueTrackingTree.xaml + /// + internal partial class ValueTrackingTree : UserControl + { + public ValueTrackingTree(ValueTrackingTreeViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + + private void ValueTrackingTreeView_PreviewKeyDown(object sender, KeyEventArgs e) + { + e.Handled = e.Handled || e.Key switch + { + Key.Down => TrySelectItem(GetNextItem(expandNode: false), navigate: true), + Key.Up => TrySelectItem(GetPreviousItem(), navigate: true), + Key.F8 => e.KeyboardDevice.Modifiers == ModifierKeys.Shift ? TrySelectItem(GetPreviousItem(), navigate: true) : TrySelectItem(GetNextItem(expandNode: true), navigate: true), + Key.Enter => TrySelectItem(ValueTrackingTreeView.SelectedItem as TreeViewItemBase, navigate: true), + Key.Right => TrySetExpanded(ValueTrackingTreeView.SelectedItem as TreeViewItemBase, true), + Key.Left => TrySetExpanded(ValueTrackingTreeView.SelectedItem as TreeViewItemBase, false), + Key.Space => TryToggleExpanded(ValueTrackingTreeView.SelectedItem as TreeViewItemBase), + _ => false + }; + + // Local Functions + + static bool TrySelectItem(TreeViewItemBase? node, bool navigate) + { + if (node is null) + { + return false; + } + + SelectItem(node, navigate); + return true; + } + + static bool TrySetExpanded(TreeViewItemBase? node, bool expanded) + { + if (node is null) + { + return false; + } + + node.IsNodeExpanded = expanded; + return true; + } + + static bool TryToggleExpanded(TreeViewItemBase? node) + { + return TrySetExpanded(node, node is null ? false : !node.IsNodeExpanded); + } + } + + private void ValueTrackingTreeView_MouseClickPreview(object sender, MouseButtonEventArgs e) + { + if (sender is TreeViewItemBase viewModel && e.ChangedButton == MouseButton.Left) + { + SelectItem(viewModel, true); + e.Handled = true; + } + } + + private static void SelectItem(TreeViewItemBase? item, bool navigate = false) + { + if (item is null) + { + return; + } + + item.IsNodeSelected = true; + if (navigate && item is TreeItemViewModel navigatableItem) + { + navigatableItem.NavigateTo(); + } + } + + private TreeViewItemBase GetNextItem(bool expandNode) + { + if (ValueTrackingTreeView.SelectedItem is null) + { + return (TreeViewItemBase)ValueTrackingTreeView.Items[0]; + } + + var item = (TreeViewItemBase)ValueTrackingTreeView.SelectedItem; + + if (expandNode) + { + item.IsNodeExpanded = true; + } + + return item.GetNextInTree(); + } + + private TreeViewItemBase GetPreviousItem() + { + if (ValueTrackingTreeView.SelectedItem is null) + { + return (TreeViewItemBase)ValueTrackingTreeView.Items[ValueTrackingTreeView.Items.Count - 1]; + } + + var item = (TreeViewItemBase)ValueTrackingTreeView.SelectedItem; + return item.GetPreviousInTree(); + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTreeRootViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTreeRootViewModel.cs new file mode 100644 index 0000000000000..7617daec33d9d --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTreeRootViewModel.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class ValueTrackingTreeRootViewModel : TreeViewItemBase + { + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTreeViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTreeViewModel.cs new file mode 100644 index 0000000000000..7dd7f79e4f368 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTreeViewModel.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Media; +using Microsoft.CodeAnalysis.Editor.ReferenceHighlighting; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.VisualStudio.Text.Classification; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + internal class ValueTrackingTreeViewModel : INotifyPropertyChanged + { + private Brush? _highlightBrush; + public Brush? HighlightBrush + { + get => _highlightBrush; + set => SetProperty(ref _highlightBrush, value); + } + + public IClassificationFormatMap ClassificationFormatMap { get; } + public ClassificationTypeMap ClassificationTypeMap { get; } + public IEditorFormatMapService FormatMapService { get; } + public ObservableCollection Roots { get; } = new(); + + private TreeItemViewModel? _selectedItem; + public TreeItemViewModel? SelectedItem + { + get => _selectedItem; + set => SetProperty(ref _selectedItem, value); + } + + private string _selectedItemFile = ""; + public string SelectedItemFile + { + get => _selectedItemFile; + set => SetProperty(ref _selectedItemFile, value); + } + + private int _selectedItemLine; + public int SelectedItemLine + { + get => _selectedItemLine; + set => SetProperty(ref _selectedItemLine, value); + } + + private bool _isLoading; + public bool IsLoading + { + get => _isLoading; + private set => SetProperty(ref _isLoading, value); + } + + private int _loadingCount; + public int LoadingCount + { + get => _loadingCount; + set => SetProperty(ref _loadingCount, value); + } + + public bool ShowDetails => SelectedItem is not null; + + public event PropertyChangedEventHandler? PropertyChanged; + + public ValueTrackingTreeViewModel(IClassificationFormatMap classificationFormatMap, ClassificationTypeMap classificationTypeMap, IEditorFormatMapService formatMapService) + { + ClassificationFormatMap = classificationFormatMap; + ClassificationTypeMap = classificationTypeMap; + FormatMapService = formatMapService; + + var properties = FormatMapService.GetEditorFormatMap("text") + .GetProperties(ReferenceHighlightTag.TagId); + + HighlightBrush = properties["Background"] as Brush; + + PropertyChanged += Self_PropertyChanged; + } + + private void Self_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(SelectedItem)) + { + SelectedItemFile = SelectedItem?.FileName ?? ""; + SelectedItemLine = SelectedItem?.LineNumber ?? 0; + NotifyPropertyChanged(nameof(ShowDetails)); + } + + if (e.PropertyName == nameof(LoadingCount)) + { + IsLoading = LoadingCount > 0; + } + } + + private void SetProperty(ref T field, T value, [CallerMemberName] string name = "") + { + if (EqualityComparer.Default.Equals(field, value)) + { + return; + } + + field = value; + NotifyPropertyChanged(name); + } + + private void NotifyPropertyChanged(string name) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } +} diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf index 0e52ac94505e0..68a57fefc19ac 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf @@ -422,6 +422,21 @@ Přejít na implementaci + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Inicializovat Interactive přes projekt diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf index 0519b3914d895..424b0c948aa2f 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf @@ -422,6 +422,21 @@ Zur Implementierung wechseln + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Interaktiv mit dem Projekt initialisieren diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf index ea66b98523deb..7afdf3f06e6af 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf @@ -422,6 +422,21 @@ Ir a la implementación + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Inicializar el elemento interactivo con el proyecto diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf index 9d160e734b01e..1dc3ee642ba0e 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf @@ -422,6 +422,21 @@ Accéder à l'implémentation + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Initialiser Interactive avec le projet diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf index eca0815f79dfd..296cb63ffdb7a 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf @@ -422,6 +422,21 @@ Vai all'implementazione + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Inizializza Interactive con il progetto diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf index c9fd9de0dacc2..f3d8a513cee51 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf @@ -422,6 +422,21 @@ 実装に移動 + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project プロジェクトでインタラクティブを初期化 diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf index f46c17cad23dc..db267369fce8b 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf @@ -422,6 +422,21 @@ 구현으로 이동 + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project 프로젝트에서 Interactive 초기화 diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf index 84daa1c5d3583..cf0051892a3ef 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf @@ -422,6 +422,21 @@ Przejdź do implementacji + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Inicjuj środowisko interaktywne z projektem diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf index 649547a61b334..44e671f80ac66 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf @@ -422,6 +422,21 @@ Ir para Implementação + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Inicializar Interativo com o Projeto diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf index 04c129f151eaa..c9f72d416a7e8 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf @@ -422,6 +422,21 @@ Перейти к реализации + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Инициализировать интерактивное окно с проектом diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf index 5b4ddbcbab5ea..4c467e7672b2f 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf @@ -422,6 +422,21 @@ Uygulamaya Git + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project Projeyi Etkileşimli Pencerede Başlat diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf index 5d051cf4b5a87..58d2499edc208 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf @@ -422,6 +422,21 @@ 转到实现 + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project 对交互窗口进行项目初始化 diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf index 96f40643fd380..1e6790f40a30e 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf @@ -422,6 +422,21 @@ 前往實作 + + Track Value Source + Track Value Source + + + + ShowValueTrackingCommandName + ShowValueTrackingCommandName + + + + ViewEditorConfigSettings + ViewEditorConfigSettings + + Initialize Interactive with Project 使用專案將 Interactive 初始化 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 2002c08bc6807..099d2851e104e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -147,6 +147,11 @@ Klient jazyka diagnostiky C# nebo Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Počítají se závislosti... @@ -242,6 +247,11 @@ Aktuální parametr + + Derived types + Derived types + + Disabled Zakázáno @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members Implementovaní členové @@ -387,6 +402,11 @@ Implementace členů + + Implementing types + Implementing types + + In other operators V jiných operátorech @@ -417,6 +437,11 @@ Míra dědičnosti (experimentální) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Vložené nápovědy (experimentální) @@ -962,6 +987,11 @@ Nastavení hledání + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Vybrat cíl @@ -1037,6 +1067,11 @@ Zobrazit míru dědičnosti + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Některé barvy barevného schématu se přepsaly změnami na stránce možností Prostředí > Písma a barvy. Pokud chcete zrušit všechna přizpůsobení, vyberte na stránce Písma a barvy možnost Použít výchozí. @@ -1172,6 +1207,11 @@ Hodnota + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used Zde přiřazená hodnota se nikdy nepoužije. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 301bfcadbfd8d..9f1002b4bfab0 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -147,6 +147,11 @@ Client für C#-/Visual Basic-Diagnosesprache + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Abhängige Objekte werden berechnet... @@ -242,6 +247,11 @@ Aktueller Parameter + + Derived types + Derived types + + Disabled Deaktiviert @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members Implementierte Member @@ -387,6 +402,11 @@ Member werden implementiert. + + Implementing types + Implementing types + + In other operators In anderen Operatoren @@ -417,6 +437,11 @@ Vererbungsrand (experimentell) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Inlinehinweise (experimentell) @@ -962,6 +987,11 @@ Sucheinstellungen + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Ziel auswählen @@ -1037,6 +1067,11 @@ Vererbungsrand anzeigen + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Einige Farbschemafarben werden durch Änderungen überschrieben, die auf der Optionsseite "Umgebung" > "Schriftarten und Farben" vorgenommen wurden. Wählen Sie auf der Seite "Schriftarten und Farben" die Option "Standardwerte verwenden" aus, um alle Anpassungen rückgängig zu machen. @@ -1172,6 +1207,11 @@ Wert + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used Der hier zugewiesene Wert wird nie verwendet. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 0c45d7110260e..969c9d02e88fd 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -147,6 +147,11 @@ Cliente de lenguaje de diagnóstico de C#/Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Calculando dependientes... @@ -242,6 +247,11 @@ Parámetro actual + + Derived types + Derived types + + Disabled Deshabilitado @@ -377,6 +387,11 @@ Id. + + Implemented interfaces + Implemented interfaces + + Implemented members Miembros implementados @@ -387,6 +402,11 @@ Implementando miembros + + Implementing types + Implementing types + + In other operators En otros operadores @@ -417,6 +437,11 @@ Margen de herencia (experimental) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Sugerencias en línea (experimental) @@ -962,6 +987,11 @@ Buscar configuración + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Seleccionar destino @@ -1037,6 +1067,11 @@ Mostrar margen de herencia + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Algunos de los colores de la combinación se reemplazan por los cambios realizados en la página de opciones de Entorno > Fuentes y colores. Elija "Usar valores predeterminados" en la página Fuentes y colores para revertir todas las personalizaciones. @@ -1172,6 +1207,11 @@ Valor + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used El valor asignado aquí no se usa nunca diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index 1f65b3053e75c..679fc6a454c49 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -147,6 +147,11 @@ Client de langage de diagnostics C#/Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Calcul des dépendants... @@ -242,6 +247,11 @@ Paramètre actuel + + Derived types + Derived types + + Disabled Désactivé @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members Membres implémentés @@ -387,6 +402,11 @@ Implémentation des membres + + Implementing types + Implementing types + + In other operators Dans les autres opérateurs @@ -417,6 +437,11 @@ Marge d’héritage (expérimental) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Indicateurs inline (expérimental) @@ -962,6 +987,11 @@ Paramètres de recherche + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Sélectionner la destination @@ -1037,6 +1067,11 @@ Afficher la marge d’héritage + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Certaines couleurs du modèle de couleurs sont substituées à la suite des changements apportés dans la page d'options Environnement > Polices et couleurs. Choisissez Utiliser les valeurs par défaut dans la page Polices et couleurs pour restaurer toutes les personnalisations. @@ -1172,6 +1207,11 @@ Valeur + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used La valeur affectée ici n'est jamais utilisée diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 0349965882bbd..5248bd7d012ab 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -147,6 +147,11 @@ Client del linguaggio di diagnostica C#/Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Calcolo dei dipendenti... @@ -242,6 +247,11 @@ Parametro corrente + + Derived types + Derived types + + Disabled Disabilitato @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members Membri implementati @@ -387,6 +402,11 @@ Membri di implementazione + + Implementing types + Implementing types + + In other operators In altri operatori @@ -417,6 +437,11 @@ Margine ereditarietà (sperimentale) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Suggerimenti inline (sperimentale) @@ -962,6 +987,11 @@ Impostazioni di ricerca + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Seleziona destinazione @@ -1037,6 +1067,11 @@ Mostra margine di ereditarietà + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Alcuni colori della combinazione colori sono sostituiti dalle modifiche apportate nella pagina di opzioni Ambiente > Tipi di carattere e colori. Scegliere `Usa impostazioni predefinite` nella pagina Tipi di carattere e colori per ripristinare tutte le personalizzazioni. @@ -1172,6 +1207,11 @@ Valore + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used Il valore assegnato qui non viene mai usato diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 23f9994b62188..9ebdfc4fada3b 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -147,6 +147,11 @@ C#/Visual Basic 診断言語クライアント + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... 依存を計算しています... @@ -242,6 +247,11 @@ 現在のパラメーター + + Derived types + Derived types + + Disabled 無効 @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members 実装されたメンバー @@ -387,6 +402,11 @@ メンバーを実装中 + + Implementing types + Implementing types + + In other operators その他の演算子内で @@ -417,6 +437,11 @@ 継承の余白 (試験段階) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) インラインのヒント (試験段階) @@ -962,6 +987,11 @@ 検索設定 + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination 宛先の選択 @@ -1037,6 +1067,11 @@ 継承の余白を表示する + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 一部の配色パターンの色は、[環境] > [フォントおよび色] オプション ページで行われた変更によって上書きされます。[フォントおよび色] オプション ページで [既定値を使用] を選択すると、すべてのカスタマイズが元に戻ります。 @@ -1172,6 +1207,11 @@ + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used ここで割り当てた値は一度も使用されません diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index ca4cc9445efba..5517b47a90ea3 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -147,6 +147,11 @@ C#/Visual Basic 진단 언어 클라이언트 + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... 종속 항목을 계산하는 중... @@ -242,6 +247,11 @@ 현재 매개 변수 + + Derived types + Derived types + + Disabled 사용 안 함 @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members 구현된 구성원 @@ -387,6 +402,11 @@ 구성원을 구현하는 중 + + Implementing types + Implementing types + + In other operators 기타 연산자 @@ -417,6 +437,11 @@ 상속 여백(실험용) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) 인라인 힌트(실험적) @@ -962,6 +987,11 @@ 검색 설정 + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination 대상 선택 @@ -1037,6 +1067,11 @@ 상속 여백 표시 + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 색 구성표의 일부 색이 [환경] > [글꼴 및 색] 옵션 페이지에서 변경한 내용에 따라 재정의됩니다. 모든 사용자 지정을 되돌리려면 [글꼴 및 색] 페이지에서 '기본값 사용'을 선택하세요. @@ -1172,6 +1207,11 @@ + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used 여기에 할당된 값은 사용되지 않습니다. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index aeb7bee13c4ad..a88cbdc00479b 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -147,6 +147,11 @@ Klient języka diagnostyki języka C#/Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Obliczanie elementów zależnych... @@ -242,6 +247,11 @@ Bieżący parametr + + Derived types + Derived types + + Disabled Wyłączone @@ -377,6 +387,11 @@ Identyfikator + + Implemented interfaces + Implemented interfaces + + Implemented members Zaimplementowane składowe @@ -387,6 +402,11 @@ Implementowanie składowych + + Implementing types + Implementing types + + In other operators W innych operatorach @@ -417,6 +437,11 @@ Margines dziedziczenia (eksperymentalny) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Wskazówki w tekście (eksperymentalne) @@ -962,6 +987,11 @@ Ustawienia wyszukiwania + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Wybierz miejsce docelowe @@ -1037,6 +1067,11 @@ Pokaż margines dziedziczenia + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Niektóre kolory w schemacie kolorów są przesłaniane przez zmiany wprowadzone na stronie opcji Środowisko > Czcionki i kolory. Wybierz pozycję „Użyj ustawień domyślnych” na stronie Czcionki i kolory, aby wycofać wszystkie dostosowania. @@ -1172,6 +1207,11 @@ Wartość + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used Przypisana tu wartość nigdy nie jest używana diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index f62f1065ba862..631be6dc09509 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -147,6 +147,11 @@ Cliente da Linguagem de Diagnóstico C#/Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Calculando dependentes... @@ -242,6 +247,11 @@ Parâmetro atual + + Derived types + Derived types + + Disabled Desabilitado @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members Membros implementados @@ -387,6 +402,11 @@ Implementando membros + + Implementing types + Implementing types + + In other operators Em outros operadores @@ -417,6 +437,11 @@ Margem de Herança (experimental) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Dicas Embutidas (experimental) @@ -962,6 +987,11 @@ Pesquisar as Configurações + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Selecionar destino @@ -1037,6 +1067,11 @@ Mostrar margem de herança + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Algumas cores do esquema de cores estão sendo substituídas pelas alterações feitas na página de Ambiente > Opções de Fontes e Cores. Escolha 'Usar Padrões' na página Fontes e Cores para reverter todas as personalizações. @@ -1172,6 +1207,11 @@ Valor + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used O valor atribuído aqui nunca é usado diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index c7e53a7d54069..43e37e7fb7556 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -147,6 +147,11 @@ Языковой клиент диагностики C#/Visual Basic + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Вычисление зависимостей… @@ -242,6 +247,11 @@ Текущий параметр + + Derived types + Derived types + + Disabled Отключено @@ -377,6 +387,11 @@ ИД + + Implemented interfaces + Implemented interfaces + + Implemented members Реализованные элементы @@ -387,6 +402,11 @@ Реализация элементов + + Implementing types + Implementing types + + In other operators В других операторах @@ -417,6 +437,11 @@ Граница наследования (экспериментальная) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Встроенные подсказки (экспериментальная функция) @@ -962,6 +987,11 @@ Параметры поиска + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Выбрать место назначения @@ -1037,6 +1067,11 @@ Показать границу наследования + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Некоторые цвета цветовой схемы переопределяются изменениями, сделанными на странице "Среда" > "Шрифты и цвета". Выберите "Использовать значения по умолчанию" на странице "Шрифты и цвета", чтобы отменить все настройки. @@ -1172,6 +1207,11 @@ Значение + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used Заданное здесь значение не используется. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index f6e7d482a1cdd..2d6bb68bb0380 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -147,6 +147,11 @@ C#/Visual Basic Tanılama Dili İstemcisi + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... Bağımlılar hesaplanıyor... @@ -242,6 +247,11 @@ Geçerli parametre + + Derived types + Derived types + + Disabled Devre dışı @@ -377,6 +387,11 @@ Kimlik + + Implemented interfaces + Implemented interfaces + + Implemented members Uygulanan üyeler @@ -387,6 +402,11 @@ Üyeleri uygulama + + Implementing types + Implementing types + + In other operators Diğer işleçlerde @@ -417,6 +437,11 @@ Devralma Marjı (deneysel) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) Satır İçi İpuçları (deneysel) @@ -962,6 +987,11 @@ Arama Ayarları + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination Hedef seçin @@ -1037,6 +1067,11 @@ Devralma boşluğunu göster + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Bazı renk düzeni renkleri, Ortam > Yazı Tipleri ve Renkler seçenek sayfasında yapılan değişiklikler tarafından geçersiz kılınıyor. Tüm özelleştirmeleri geri döndürmek için Yazı Tipleri ve Renkler sayfasında `Varsayılanları Kullan` seçeneğini belirleyin. @@ -1172,6 +1207,11 @@ Değer + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used Burada atanan değer hiçbir zaman kullanılmadı diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 793a652ff626f..3496d40d78a79 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -147,6 +147,11 @@ C#/Visual Basic 语言服务器客户端 + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... 正在计算依赖项... @@ -242,6 +247,11 @@ 当前参数 + + Derived types + Derived types + + Disabled 已禁用 @@ -377,6 +387,11 @@ ID + + Implemented interfaces + Implemented interfaces + + Implemented members 实现的成员 @@ -387,6 +402,11 @@ 正在实现成员 + + Implementing types + Implementing types + + In other operators 在其他运算符中 @@ -417,6 +437,11 @@ 继承边距(实验性) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) 内联提示(实验性) @@ -962,6 +987,11 @@ 搜索设置 + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination 选择目标 @@ -1037,6 +1067,11 @@ 显示继承边距 + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 在“环境”>“字体和颜色”选项页中所做的更改将替代某些配色方案颜色。在“字体和颜色”页中选择“使用默认值”,还原所有自定义项。 @@ -1172,6 +1207,11 @@ + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used 此处分配的值从未使用过 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 3da8a86c54954..aa3dee1116470 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -147,6 +147,11 @@ C#/Visual Basic 診斷語言用戶端 + + Calculating... + Calculating... + Used in UI to represent progress in the context of loading items. + Calculating dependents... 正在計算相依項... @@ -242,6 +247,11 @@ 目前的參數 + + Derived types + Derived types + + Disabled 已停用 @@ -377,6 +387,11 @@ 識別碼 + + Implemented interfaces + Implemented interfaces + + Implemented members 已實作的成員 @@ -387,6 +402,11 @@ 實作成員 + + Implementing types + Implementing types + + In other operators 其他運算子中 @@ -417,6 +437,11 @@ 繼承邊界 (實驗性) + + Inherited interfaces + Inherited interfaces + + Inline Hints (experimental) 內嵌提示 (實驗性) @@ -962,6 +987,11 @@ 搜尋設定 + + Select an appropriate symbol to start value tracking + Select an appropriate symbol to start value tracking + + Select destination 選取目的地 @@ -1037,6 +1067,11 @@ 顯示繼承邊界 + + Skip analyzers for implicitly triggered builds + Skip analyzers for implicitly triggered builds + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. [環境] > [字型和色彩選項] 頁面中所做的變更覆寫了某些色彩配置的色彩。請選擇 [字型和色彩] 頁面中的 [使用預設] 來還原所有自訂。 @@ -1172,6 +1207,11 @@ + + Value Tracking + Value Tracking + Title of the "Value Tracking" tool window. "Value" is the variable/symbol and "Tracking" is the action the tool is doing to follow where the value can originate from. + Value assigned here is never used 這裡指派的值從未使用過 diff --git a/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs b/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs index 653f5f78d8f3b..f1771f36fcc1a 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs @@ -38,6 +38,8 @@ public CodeModelState( Debug.Assert(languageServices != null); Debug.Assert(workspace != null); + // ⚠ This code runs on the main thread. Language services accessed here should be preloaded in + // ProjectCodemodelFactory to avoid blocking MEF operations. this.ThreadingContext = threadingContext; this.ServiceProvider = serviceProvider; this.CodeModelService = languageServices.GetService(); diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 3d679fbd43688..1a545d92168cc 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -7,15 +7,18 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using Task = System.Threading.Tasks.Task; @@ -53,7 +56,7 @@ public ProjectCodeModelFactory( // for the same documents. Once enough time has passed, take the documents that were changed and run // through them, firing their latest events. _documentsToFireEventsFor = new AsyncBatchingWorkQueue( - TimeSpan.FromMilliseconds(visualStudioWorkspace.Options.GetOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS)), + InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpan, ProcessNextDocumentBatchAsync, // We only care about unique doc-ids, so pass in this comparer to collapse streams of changes for a // single document down to one notification. @@ -66,7 +69,7 @@ public ProjectCodeModelFactory( internal IAsynchronousOperationListener Listener { get; } - private async Task ProcessNextDocumentBatchAsync( + private async ValueTask ProcessNextDocumentBatchAsync( ImmutableArray documentIds, CancellationToken cancellationToken) { // This logic preserves the previous behavior we had with IForegroundNotificationService. @@ -76,6 +79,27 @@ private async Task ProcessNextDocumentBatchAsync( const int MaxTimeSlice = 15; var delayBetweenProcessing = TimeSpan.FromMilliseconds(50); + Debug.Assert(!_threadingContext.JoinableTaskContext.IsOnMainThread, "The following context switch is not expected to cause runtime overhead."); + await TaskScheduler.Default; + + // Ensure MEF services used by the code model are initially obtained on a background thread. + // This code avoids allocations where possible. + // https://github.com/dotnet/roslyn/issues/54159 + string? previousLanguage = null; + foreach (var (_, projectState) in _visualStudioWorkspace.CurrentSolution.State.ProjectStates) + { + if (projectState.Language == previousLanguage) + { + // Avoid duplicate calls if the language did not change + continue; + } + + previousLanguage = projectState.Language; + projectState.LanguageServices.GetService(); + projectState.LanguageServices.GetService(); + projectState.LanguageServices.GetService(); + } + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var stopwatch = SharedStopwatch.StartNew(); diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/TempPECompiler.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/TempPECompiler.cs index e1b9ee9d11ab9..5d07e889d16a4 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/TempPECompiler.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/TempPECompiler.cs @@ -33,6 +33,7 @@ public async Task CompileAsync(IWorkspaceProjectContext context, string ou { throw new ArgumentException(nameof(filesToInclude), "Must specify some files to compile."); } + if (outputFileName == null) { throw new ArgumentException(nameof(outputFileName), "Must specify an output file name."); diff --git a/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb b/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb index dc9bb381a0551..453be35d609ec 100644 --- a/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb +++ b/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb @@ -159,6 +159,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CommonControls If (member Is Nothing) Then Assert.True(False, $"No member called {name} found") End If + Return member End Function diff --git a/src/VisualStudio/Core/Test/DebuggerIntelliSense/CSharpDebuggerIntellisenseTests.vb b/src/VisualStudio/Core/Test/DebuggerIntelliSense/CSharpDebuggerIntellisenseTests.vb index db75eb5203961..930f0e025a77e 100644 --- a/src/VisualStudio/Core/Test/DebuggerIntelliSense/CSharpDebuggerIntellisenseTests.vb +++ b/src/VisualStudio/Core/Test/DebuggerIntelliSense/CSharpDebuggerIntellisenseTests.vb @@ -145,6 +145,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.DebuggerIntelliSense For i As Integer = 0 To 7 state.SendBackspace() Next + Await state.AssertNoCompletionSession() state.SendTypeChars("green") diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index e4fcc5e1edf83..1acc49cc93025 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -246,6 +246,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Assert.Equal(1, diagnostics.Length) Assert.Equal(diagnostics(0).Properties(WellKnownDiagnosticPropertyNames.Origin), WellKnownDiagnosticTags.Build) End Sub + source.OnSolutionBuildCompleted() Await waiter.ExpeditedWaitAsync() diff --git a/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb index d8c2d3b1bf24c..072ef91f7774d 100644 --- a/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb +++ b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb @@ -29,7 +29,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.InheritanceMargin Private Shared s_defaultMargin As Thickness = New Thickness(4, 1, 4, 1) - Private Shared s_indentMargin As Thickness = New Thickness(20, 1, 4, 1) + Private Shared s_indentMargin As Thickness = New Thickness(22, 1, 4, 1) Private Shared Async Function VerifyAsync(markup As String, languageName As String, expectedViewModels As Dictionary(Of Integer, InheritanceMarginViewModel)) As Task ' Add an lf before the document so that the line number starts @@ -96,6 +96,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.InheritanceMargin For i = 0 To expected.MenuItemViewModels.Length - 1 Dim expectedMenuItem = expected.MenuItemViewModels(i) Dim actualMenuItem = acutal.MenuItemViewModels(i) + VerifyMenuItem(expectedMenuItem, actualMenuItem) Next End Sub @@ -108,17 +109,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.InheritanceMargin Dim expectedTargetMenuItem = TryCast(expected, TargetMenuItemViewModel) Dim acutalTargetMenuItem = TryCast(actual, TargetMenuItemViewModel) If expectedTargetMenuItem IsNot Nothing AndAlso acutalTargetMenuItem IsNot Nothing Then - Assert.Equal(expectedTargetMenuItem.Margin, acutalTargetMenuItem.Margin) Return End If Dim expectedMemberMenuItem = TryCast(expected, MemberMenuItemViewModel) Dim acutalMemberMenuItem = TryCast(actual, MemberMenuItemViewModel) - If expectedTargetMenuItem IsNot Nothing AndAlso acutalTargetMenuItem IsNot Nothing Then + If expectedMemberMenuItem IsNot Nothing AndAlso acutalMemberMenuItem IsNot Nothing Then Assert.Equal(expectedMemberMenuItem.Targets.Length, acutalMemberMenuItem.Targets.Length) - For i = 0 To expectedMemberMenuItem.Targets.Length + For i = 0 To expectedMemberMenuItem.Targets.Length - 1 VerifyMenuItem(expectedMemberMenuItem.Targets(i), acutalMemberMenuItem.Targets(i)) Next + Return End If @@ -143,12 +144,12 @@ public class Bar : IBar { }" Dim tooltipTextForIBar = String.Format(ServicesVSResources._0_is_inherited, "interface IBar") - Dim targetForIBar = ImmutableArray.Create( - New TargetMenuItemViewModel("Bar", KnownMonikers.ClassPublic, "Bar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForIBar = ImmutableArray.Create(Of InheritanceMenuItemViewModel)(New HeaderMenuItemViewModel(ServicesVSResources.Implementing_types, KnownMonikers.Implemented, ServicesVSResources.Implementing_types)). + Add(New TargetMenuItemViewModel("Bar", KnownMonikers.ClassPublic, "Bar", Nothing)) Dim tooltipTextForBar = String.Format(ServicesVSResources._0_is_inherited, "class Bar") - Dim targetForBar = ImmutableArray.Create( - New TargetMenuItemViewModel("IBar", KnownMonikers.InterfacePublic, "IBar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForBar = ImmutableArray.Create(Of InheritanceMenuItemViewModel)(New HeaderMenuItemViewModel(ServicesVSResources.Implemented_interfaces, KnownMonikers.Implementing, ServicesVSResources.Implemented_interfaces)). + Add(New TargetMenuItemViewModel("IBar", KnownMonikers.InterfacePublic, "IBar", Nothing)) Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { {2, New InheritanceMarginViewModel( @@ -179,24 +180,24 @@ public class Bar : AbsBar }" Dim tooltipTextForAbsBar = String.Format(ServicesVSResources._0_is_inherited, "class AbsBar") - Dim targetForAbsBar = ImmutableArray.Create( - New TargetMenuItemViewModel("Bar", KnownMonikers.Class, "Bar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForAbsBar = ImmutableArray.Create(Of InheritanceMenuItemViewModel)(New HeaderMenuItemViewModel(ServicesVSResources.Derived_types, KnownMonikers.Overridden, ServicesVSResources.Derived_types)). + Add(New TargetMenuItemViewModel("Bar", KnownMonikers.ClassPublic, "Bar", Nothing)) Dim tooltipTextForAbstractFoo = String.Format(ServicesVSResources._0_is_inherited, "abstract void AbsBar.Foo()") - Dim targetForAbsFoo = ImmutableArray.Create( - New TargetMenuItemViewModel("AbsBar.Foo()", KnownMonikers.MethodPublic, "AbsBar.Foo()", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForAbsFoo = ImmutableArray.Create(Of InheritanceMenuItemViewModel)(New HeaderMenuItemViewModel(ServicesVSResources.Overriding_members, KnownMonikers.Overridden, ServicesVSResources.Overriding_members)). + Add(New TargetMenuItemViewModel("Bar.Foo", KnownMonikers.MethodPublic, "Bar.Foo", Nothing)) Dim tooltipTextForBar = String.Format(ServicesVSResources._0_is_inherited, "class Bar") - Dim targetForBar = ImmutableArray.Create( - New TargetMenuItemViewModel("AbsBar", KnownMonikers.Class, "AbsBar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForBar = ImmutableArray.Create(Of InheritanceMenuItemViewModel)(New HeaderMenuItemViewModel(ServicesVSResources.Base_Types, KnownMonikers.Overriding, ServicesVSResources.Base_Types)). + Add(New TargetMenuItemViewModel("AbsBar", KnownMonikers.ClassPublic, "AbsBar", Nothing)) Dim tooltipTextForOverrideFoo = String.Format(ServicesVSResources._0_is_inherited, "override void Bar.Foo()") - Dim targetForOverrideFoo = ImmutableArray.Create( - New TargetMenuItemViewModel("Bar.Foo()", KnownMonikers.MethodPublic, "Bar.Foo()", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForOverrideFoo = ImmutableArray.Create(Of InheritanceMenuItemViewModel)(New HeaderMenuItemViewModel(ServicesVSResources.Overridden_members, KnownMonikers.Overriding, ServicesVSResources.Overridden_members)). + Add(New TargetMenuItemViewModel("AbsBar.Foo", KnownMonikers.MethodPublic, "AbsBar.Foo", Nothing)) Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { {2, New InheritanceMarginViewModel( - KnownMonikers.Implemented, + KnownMonikers.Overridden, CreateTextBlock(tooltipTextForAbsBar), tooltipTextForAbsBar, 1, @@ -208,7 +209,7 @@ public class Bar : AbsBar 1, targetForAbsFoo)}, {7, New InheritanceMarginViewModel( - KnownMonikers.Implementing, + KnownMonikers.Overriding, CreateTextBlock(tooltipTextForBar), tooltipTextForBar, 1, @@ -229,21 +230,23 @@ public interface IBar2 : IBar1 { } public interface IBar3 : IBar2 { } " Dim tooltipTextForIBar1 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar1") - Dim targetForIBar1 = ImmutableArray.Create( - New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing, s_defaultMargin), - New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForIBar1 = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_types, KnownMonikers.Implemented, ServicesVSResources.Implementing_types), + New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing), + New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing)) Dim tooltipTextForIBar2 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar2") Dim targetForIBar2 = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( - New HeaderMenuItemViewModel(ServicesVSResources.Implementing_members, KnownMonikers.Implementing, ServicesVSResources.Implementing_members), - New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_indentMargin), - New HeaderMenuItemViewModel(ServicesVSResources.Implemented_members, KnownMonikers.Implemented, ServicesVSResources.Implemented_members), - New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing, s_indentMargin)) + New HeaderMenuItemViewModel(ServicesVSResources.Inherited_interfaces, KnownMonikers.Implementing, ServicesVSResources.Inherited_interfaces), + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing), + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_types, KnownMonikers.Implemented, ServicesVSResources.Implementing_types), + New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing)) Dim tooltipTextForIBar3 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar3") - Dim targetForIBar3 = ImmutableArray.Create( - New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_defaultMargin), - New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForIBar3 = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Inherited_interfaces, KnownMonikers.Implementing, ServicesVSResources.Inherited_interfaces), + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing), + New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing)).CastArray(Of InheritanceMenuItemViewModel) Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { {2, New InheritanceMarginViewModel( @@ -253,7 +256,7 @@ public interface IBar3 : IBar2 { } 1, targetForIBar1)}, {3, New InheritanceMarginViewModel( - KnownMonikers.Implemented, + KnownMonikers.Implementing, CreateTextBlock(tooltipTextForIBar2), tooltipTextForIBar2, 1, @@ -281,27 +284,32 @@ public class BarSample : IBar1 }" Dim tooltipTextForIBar1 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar1") - Dim targetForIBar1 = ImmutableArray.Create( - New TargetMenuItemViewModel("BarSample", KnownMonikers.ClassPublic, "BarSample", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForIBar1 = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_types, KnownMonikers.Implemented, ServicesVSResources.Implementing_types), + New TargetMenuItemViewModel("BarSample", KnownMonikers.ClassPublic, "BarSample", Nothing)) Dim tooltipTextForE1AndE2InInterface = ServicesVSResources.Multiple_members_are_inherited Dim targetForE1AndE2InInterface = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( - New MemberMenuItemViewModel("event EventHandler e1", KnownMonikers.EventPublic, "event EventHandler e1", ImmutableArray.Create( - New TargetMenuItemViewModel("BarSample.e1", KnownMonikers.EventPublic, "BarSample.e1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel)), - New MemberMenuItemViewModel("event EventHandler e2", KnownMonikers.EventPublic, "event EventHandler e2", ImmutableArray.Create( - New TargetMenuItemViewModel("BarSample.e2", KnownMonikers.EventPublic, "BarSample.e2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel))) _ - .CastArray(Of InheritanceMenuItemViewModel) + New MemberMenuItemViewModel("event EventHandler IBar1.e1", KnownMonikers.EventPublic, "event EventHandler IBar1.e1", ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_members, KnownMonikers.Implemented, ServicesVSResources.Implementing_members), + New TargetMenuItemViewModel("BarSample.e1", KnownMonikers.EventPublic, "BarSample.e1", Nothing))), + New MemberMenuItemViewModel("event EventHandler IBar1.e2", KnownMonikers.EventPublic, "event EventHandler IBar1.e2", ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_members, KnownMonikers.Implemented, ServicesVSResources.Implementing_members), + New TargetMenuItemViewModel("BarSample.e2", KnownMonikers.EventPublic, "BarSample.e2", Nothing)))) Dim tooltipTextForBarSample = String.Format(ServicesVSResources._0_is_inherited, "class BarSample") - Dim targetForBarSample = ImmutableArray.Create( - New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + Dim targetForBarSample = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implemented_interfaces, KnownMonikers.Implementing, ServicesVSResources.Implemented_interfaces), + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfaceInternal, "IBar1", Nothing)) Dim tooltipTextForE1AndE2InBarSample = ServicesVSResources.Multiple_members_are_inherited Dim targetForE1AndE2InInBarSample = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( - New MemberMenuItemViewModel("event EventHandler e1", KnownMonikers.EventPublic, "event EventHandler e1", ImmutableArray.Create( - New TargetMenuItemViewModel("IBar1.BarSample.e1", KnownMonikers.EventPublic, "IBar1.BarSample.e1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel)), - New MemberMenuItemViewModel("event EventHandler e2", KnownMonikers.EventPublic, "event EventHandler e2", ImmutableArray.Create( - New TargetMenuItemViewModel("IBar1.BarSample.e2", KnownMonikers.EventPublic, "IBar1.BarSample.e2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel))) _ + New MemberMenuItemViewModel("virtual event EventHandler BarSample.e1", KnownMonikers.EventPublic, "virtual event EventHandler BarSample.e1", ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implemented_members, KnownMonikers.Implementing, ServicesVSResources.Implemented_members), + New TargetMenuItemViewModel("IBar1.e1", KnownMonikers.EventPublic, "IBar1.e1", Nothing)).CastArray(Of InheritanceMenuItemViewModel)), + New MemberMenuItemViewModel("virtual event EventHandler BarSample.e2", KnownMonikers.EventPublic, "virtual event EventHandler BarSample.e2", ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implemented_members, KnownMonikers.Implementing, ServicesVSResources.Implemented_members), + New TargetMenuItemViewModel("IBar1.e2", KnownMonikers.EventPublic, "IBar1.e2", Nothing)).CastArray(Of InheritanceMenuItemViewModel))) _ .CastArray(Of InheritanceMenuItemViewModel) Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 3495a0864e91f..2cf6212cbe614 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -133,6 +133,7 @@ csharp_style_implicit_object_creation_when_type_is_apparent = true csharp_style_inlined_variable_declaration = true csharp_style_pattern_local_over_anonymous_function = true csharp_style_prefer_index_operator = true +csharp_style_prefer_null_check_over_type_check = true csharp_style_prefer_range_operator = true csharp_style_throw_expression = true csharp_style_unused_value_assignment_preference = discard_variable @@ -362,6 +363,7 @@ csharp_style_implicit_object_creation_when_type_is_apparent = true csharp_style_inlined_variable_declaration = true csharp_style_pattern_local_over_anonymous_function = true csharp_style_prefer_index_operator = true +csharp_style_prefer_null_check_over_type_check = true csharp_style_prefer_range_operator = true csharp_style_throw_expression = true csharp_style_unused_value_assignment_preference = discard_variable diff --git a/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb b/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb index e0ea73d5773e5..1a6bc6d74996d 100644 --- a/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb +++ b/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb @@ -16,7 +16,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Public Sub TestGetContainsGraphQueries() Dim context = CreateGraphContext(GraphContextDirection.Contains, Array.Empty(Of GraphCategory)()) - Dim queries = AbstractGraphProvider.GetGraphQueries(context) + Dim queries = AbstractGraphProvider.GetGraphQueries(context, threadingContext:=Nothing, asyncListener:=Nothing) Assert.Equal(queries.Single().GetType(), GetType(ContainsGraphQuery)) End Sub @@ -24,7 +24,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Public Sub TestGetContainsGraphQueriesWithTarget() Dim context = CreateGraphContext(GraphContextDirection.Target, {CodeLinkCategories.Contains}) - Dim queries = AbstractGraphProvider.GetGraphQueries(context) + Dim queries = AbstractGraphProvider.GetGraphQueries(context, threadingContext:=Nothing, asyncListener:=Nothing) Assert.Equal(queries.Single().GetType(), GetType(ContainsGraphQuery)) End Sub diff --git a/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb b/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb index 59d22d1e0b23a..a534cee5a3462 100644 --- a/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb +++ b/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb @@ -12,6 +12,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Private ReadOnly _direction As GraphContextDirection Private ReadOnly _graph As Graph Private ReadOnly _inputNodes As ISet(Of GraphNode) + Private ReadOnly _outputNodes As New HashSet(Of GraphNode) Public Sub New(direction As GraphContextDirection, graph As Graph, inputNodes As IEnumerable(Of GraphNode)) _direction = direction @@ -89,7 +90,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Public ReadOnly Property OutputNodes As ISet(Of GraphNode) Implements IGraphContext.OutputNodes Get - Throw New NotImplementedException() + Return _outputNodes End Get End Property diff --git a/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb b/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb index 2b39583ca0074..687cfb38de5c2 100644 --- a/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb +++ b/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb @@ -3,7 +3,6 @@ ' See the LICENSE file in the project root for more information. Imports System.Threading -Imports System.Threading.Tasks Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.FindSymbols @@ -14,10 +13,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Friend Class ProgressionTestState Implements IDisposable - Private ReadOnly _workspace As TestWorkspace + Public ReadOnly Workspace As TestWorkspace Public Sub New(workspace As TestWorkspace) - _workspace = workspace + Me.Workspace = workspace End Sub Public Shared Function Create(workspaceXml As XElement) As ProgressionTestState @@ -27,30 +26,30 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression End Function Public Function GetGraphWithDocumentNode(filePath As String) As Graph - Dim graphBuilder As New GraphBuilder(_workspace.CurrentSolution, CancellationToken.None) - Dim documentId = _workspace.Documents.Single(Function(d) d.FilePath = filePath).Id - graphBuilder.AddNodeForDocument(_workspace.CurrentSolution.GetDocument(documentId)) + Dim graphBuilder As New GraphBuilder(Workspace.CurrentSolution) + Dim documentId = Workspace.Documents.Single(Function(d) d.FilePath = filePath).Id + graphBuilder.AddNodeForDocument(Workspace.CurrentSolution.GetDocument(documentId), CancellationToken.None) Return graphBuilder.Graph End Function Public Async Function GetGraphWithMarkedSymbolNodeAsync(Optional symbolTransform As Func(Of ISymbol, ISymbol) = Nothing) As Task(Of Graph) - Dim hostDocument As TestHostDocument = _workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim document = _workspace.CurrentSolution.GetDocument(hostDocument.Id) + Dim hostDocument As TestHostDocument = Workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) + Dim document = Workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim symbol = Await GetMarkedSymbolAsync() If symbolTransform IsNot Nothing Then symbol = symbolTransform(symbol) End If - Dim graphBuilder As New GraphBuilder(_workspace.CurrentSolution, CancellationToken.None) - graphBuilder.AddNodeAsync(symbol, document.Project, document).Wait(CancellationToken.None) + Dim graphBuilder As New GraphBuilder(Workspace.CurrentSolution) + Await graphBuilder.AddNodeAsync(symbol, document.Project, document, CancellationToken.None) Return graphBuilder.Graph End Function Public Async Function GetGraphContextAfterQuery(graph As Graph, graphQuery As IGraphQuery, direction As GraphContextDirection) As Task(Of IGraphContext) Dim graphContext As New MockGraphContext(direction, graph.Copy(), graph.Nodes) - Dim graphBuilder = Await graphQuery.GetGraphAsync(_workspace.CurrentSolution, graphContext, CancellationToken.None) - graphBuilder.ApplyToGraph(graphContext.Graph) + Dim graphBuilder = Await graphQuery.GetGraphAsync(Workspace.CurrentSolution, graphContext, CancellationToken.None) + graphBuilder.ApplyToGraph(graphContext.Graph, CancellationToken.None) Return graphContext End Function @@ -58,13 +57,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Public Async Function GetGraphContextAfterQueryWithSolution(graph As Graph, solution As Solution, graphQuery As IGraphQuery, direction As GraphContextDirection) As Task(Of IGraphContext) Dim graphContext As New MockGraphContext(direction, graph.Copy(), graph.Nodes) Dim graphBuilder = Await graphQuery.GetGraphAsync(solution, graphContext, CancellationToken.None) - graphBuilder.ApplyToGraph(graphContext.Graph) + graphBuilder.ApplyToGraph(graphContext.Graph, CancellationToken.None) Return graphContext End Function Private Sub Dispose() Implements IDisposable.Dispose - _workspace.Dispose() + Workspace.Dispose() End Sub Public Async Function AssertMarkedSymbolLabelIsAsync(graphCommandId As String, label As String, description As String) As Task @@ -76,13 +75,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression End Function Public Function GetMarkedSymbolAsync() As Task(Of ISymbol) - Dim hostDocument As TestHostDocument = _workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim document = _workspace.CurrentSolution.GetDocument(hostDocument.Id) + Dim hostDocument As TestHostDocument = Workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) + Dim document = Workspace.CurrentSolution.GetDocument(hostDocument.Id) Return SymbolFinder.FindSymbolAtPositionAsync(document, hostDocument.CursorPosition.Value) End Function Public Function GetSolution() As Solution - Return _workspace.CurrentSolution + Return Workspace.CurrentSolution End Function End Class End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb index 1bd40c1b07f67..ff84d6ee40eb9 100644 --- a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb +++ b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb @@ -3,7 +3,9 @@ ' See the LICENSE file in the project root for more information. Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.GraphModel Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression @@ -23,7 +25,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="C"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="C", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -55,7 +59,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="F"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="F", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -89,7 +95,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="M"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="M", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -136,7 +144,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="C"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="C", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -184,7 +194,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="Goo"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="Goo", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -231,7 +243,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="Z"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="Z", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -270,7 +284,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="D.B"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -304,7 +320,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="C.B"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="C.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -326,7 +344,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="D.B"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -360,7 +380,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="A.D.B"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="A.D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -394,7 +416,9 @@ End Namespace ) - Dim outputContext = Await testState.GetGraphContextAfterQuery(New Graph(), New SearchGraphQuery(searchPattern:="A.D.B"), GraphContextDirection.Custom) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery(searchPattern:="A.D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) ' When searching, don't descend into projects with a null FilePath because they are artifacts and not ' representable in the Solution Explorer, e.g., Venus projects create sub-projects with a null file diff --git a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb new file mode 100644 index 0000000000000..c109b45bd8e82 --- /dev/null +++ b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb @@ -0,0 +1,430 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Shared.TestHooks +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.GraphModel +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression +Imports Roslyn.Test.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression + + Public Class SearchGraphQueryTests_NavigateTo + + Public Async Function SearchForType() As Task + Using testState = ProgressionTestState.Create( + + + + class C { } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("C", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForNestedType() As Task + Using testState = ProgressionTestState.Create( + + + + class C { class F { } } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("F", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForMember() As Task + Using testState = ProgressionTestState.Create( + + + + class C { void M(); } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("M", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForPartialType() As Task + Using testState = ProgressionTestState.Create( + + + +Namespace N + Partial Class C + Sub Goo() + End Sub + End Class +End Namespace + + +Namespace N + Partial Class C + Sub Bar() + End Sub + End Class +End Namespace + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("C", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForMethodInPartialType() As Task + Using testState = ProgressionTestState.Create( + + + +Namespace N + Partial Class C + Sub Goo() + End Sub + End Class +End Namespace + + +Namespace N + Partial Class C + Sub Bar() + End Sub + End Class +End Namespace + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("Goo", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchWithResultsAcrossMultipleTypeParts() As Task + Using testState = ProgressionTestState.Create( + + + +Namespace N + Partial Class C + Sub ZGoo() + End Sub + End Class +End Namespace + + +Namespace N + Partial Class C + Sub ZBar() + End Sub + End Class +End Namespace + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("Z", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForDottedName1() As Task + Using testState = ProgressionTestState.Create( + + + + class Dog { void Bark() { } } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForDottedName2() As Task + Using testState = ProgressionTestState.Create( + + + + class Dog { void Bark() { } } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("C.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + ) + End Using + End Function + + + Public Async Function SearchForDottedName3() As Task + Using testState = ProgressionTestState.Create( + + + + namespace Animal { class Dog<X> { void Bark() { } } } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchForDottedName4() As Task + Using testState = ProgressionTestState.Create( + + + + namespace Animal { class Dog<X> { void Bark() { } } } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("A.D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + + + + + + + + + + ) + End Using + End Function + + + Public Async Function SearchWithNullFilePathsOnProject() As Task + Using testState = ProgressionTestState.Create( + + > + + namespace Animal { class Dog<X> { void Bark() { } } } + + + ) + + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, True)) + Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) + Dim outputContext = Await testState.GetGraphContextAfterQuery( + New Graph(), New SearchGraphQuery("A.D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + + ' When searching, don't descend into projects with a null FilePath because they are artifacts and not + ' representable in the Solution Explorer, e.g., Venus projects create sub-projects with a null file + ' path for each .aspx file. Documents, on the other hand, are never allowed to have a null file path + ' and as such are not tested here. The project/document structure for these scenarios would look + ' similar to this: + ' + ' Project: SomeVenusProject, FilePath=C:\path\to\project.csproj + ' + Document: SomeVenusDocument.aspx, FilePath=C:\path\to\SomeVenusDocument.aspx + ' + Project: 1_SomeNamespace_SomeVenusDocument.aspx, FilePath=null <- the problem is here + ' + Document: SomeVenusDocument.aspx.cs + AssertSimplifiedGraphIs( + outputContext.Graph, + + + + ) + End Using + End Function + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb b/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb index 85d1ba5e6d7c0..88c32b9cc177a 100644 --- a/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb +++ b/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb @@ -230,6 +230,7 @@ class MyClass : Level1BaseClass, Level1Interface If (member Is Nothing) Then Assert.True(False, $"No member called {name} found") End If + Return member End Function diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 81b813e0e5abe..b7a0aac19bbd2 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -19,7 +19,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Public Sub SourceGeneratorsListed() Dim workspaceXml = - + @@ -39,7 +39,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Public Async Function PlaceholderItemCreateIfGeneratorProducesNoFiles() As Task Dim workspaceXml = - + @@ -63,7 +63,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Public Async Function SingleSourceGeneratedFileProducesItem() As Task Dim workspaceXml = - + @@ -92,7 +92,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Public Async Function MultipleSourceGeneratedFilesProducesSortedItem() As Task Dim workspaceXml = - + @@ -124,7 +124,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Public Async Function ChangeToNoGeneratedDocumentsUpdatesListCorrectly() As Task Dim workspaceXml = - + @@ -153,7 +153,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Public Async Function AddingAGeneratedDocumentUpdatesListCorrectly() As Task Dim workspaceXml = - + diff --git a/src/VisualStudio/Core/Test/SymbolSearch/SymbolSearchUpdateEngineTests.vb b/src/VisualStudio/Core/Test/SymbolSearch/SymbolSearchUpdateEngineTests.vb index 7817dda6ff145..8df2aa72bc255 100644 --- a/src/VisualStudio/Core/Test/SymbolSearch/SymbolSearchUpdateEngineTests.vb +++ b/src/VisualStudio/Core/Test/SymbolSearch/SymbolSearchUpdateEngineTests.vb @@ -619,6 +619,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SymbolSearch Else SetupDownloadPatch(clientMock, remoteControlMock) End If + Return remoteControlMock End Function diff --git a/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb b/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb index 3ec77f7b60a46..6659a8a2cc26f 100644 --- a/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb +++ b/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb @@ -19,11 +19,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnusedReferences Dim libraries = BuildLibraries(allReferences) Dim targets = BuildTargets(targetFramework, allReferences) + Dim project = BuildProject(targetFramework) Dim projectAssets As ProjectAssetsFile = New ProjectAssetsFile With { .Version = version, .Targets = targets, - .Libraries = libraries + .Libraries = libraries, + .Project = project } Return projectAssets @@ -88,5 +90,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnusedReferences Return String.Empty End Function) End Function + + Private Function BuildProject(targetFramework As String) As ProjectAssetsProject + ' Frameworks won't always specify a set of dependencies. + ' This ensures the project asset reader does not error in these cases. + Return New ProjectAssetsProject With { + .Frameworks = New Dictionary(Of String, ProjectAssetsProjectFramework) From { + {targetFramework, New ProjectAssetsProjectFramework} + } + } + End Function End Module End Namespace diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs index 219bebc36f675..bb73bb99f91b5 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs @@ -373,5 +373,38 @@ public void M(string s, int i, int i2) VisualStudio.Editor.SendKeys(VirtualKey.Down); VisualStudio.Editor.Verify.CurrentLineText("M(" + parameterText + ", 0, 0)"); } + + [WpfFact] + [WorkItem(54038, "https://github.com/dotnet/roslyn/issues/54038")] + public void InsertPreprocessorSnippet() + { + SetUpEditor(@" +using System; +public class TestClass +{ +$$ +} +"); + + VisualStudio.Editor.SendKeys("#i"); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("#if$$", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("#if true$$", assertCaretPosition: true); + + var expected = @" +using System; +public class TestClass +{ +#if true + +#endif +} +"; + + AssertEx.EqualOrDiff(expected, VisualStudio.Editor.GetText()); + } } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs index 21a550b58ec2f..4e648b6deea9d 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs @@ -37,6 +37,7 @@ public Reference[] GetContents(string windowCaption) groupingPriority: 0); newColumnsStates.Add(newState); } + tableControl.SetColumnStates(newColumnsStates); // Force a refresh, if necessary. This doesn't re-run the Find References or diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs index 4a4c5637182da..7d198c3c9398c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs @@ -192,6 +192,7 @@ public void EditProjectFile(string projectName) { throw new ArgumentException($"Could not find project file, current hierarchy items '{string.Join(", ", rootHierarchyItems.Select(x => x.Name))}'"); } + project.Select(EnvDTE.vsUISelectionType.vsUISelectionTypeSelect); ExecuteCommand("Project.EditProjectFile"); } @@ -210,6 +211,7 @@ public void CreateSolution(string solutionName, string solutionElementString) { throw new ArgumentException(nameof(solutionElementString)); } + CreateSolution(solutionName); foreach (var projectElement in solutionElement.Elements("Project")) @@ -313,6 +315,7 @@ public void RemoveProjectReference(string projectName, string projectReferenceNa var projectReference = references.Where(x => x.ContainingProject != null).Select(x => x.Name); throw new ArgumentException($"reference to project {projectReferenceName} not found, references: '{string.Join(", ", projectReference)}'"); } + reference.Remove(); } @@ -324,6 +327,7 @@ public void OpenSolution(string path, bool saveExistingSolutionIfExists = false) { CloseSolution(saveExistingSolutionIfExists); } + dte.Solution.Open(path); _solution = (EnvDTE80.Solution2)dte.Solution; @@ -806,6 +810,7 @@ int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) { pfCancelUpdate = 1; } + return 0; } @@ -839,6 +844,7 @@ int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate) { pfCancelUpdate = 1; } + return 0; } @@ -884,6 +890,7 @@ private int UpdateSolution_StartUpdate(ref int pfCancelUpdate) { pfCancelUpdate = 1; } + return 0; } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs index b4106b5aaede0..dc52c2ba9508c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs @@ -300,6 +300,7 @@ bool filterTag(IMappingTagSpan tag) { return tag.Tag.GetType().Equals(type); } + var service = GetComponentModelService(); var aggregator = service.CreateTagAggregator(view); var allTags = aggregator.GetTags(new SnapshotSpan(view.TextSnapshot, 0, view.TextSnapshot.Length)); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs index 2428414f901d7..2ed1280bb6298 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs @@ -276,6 +276,7 @@ private static ISetupInstance LocateVisualStudioInstance(ImmutableHashSet(object json, out T t) { return true; } + try { t = ((JObject)json).ToObject(); diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteDiagnosticListTable.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteDiagnosticListTable.cs index 5d407bad2c7c9..5cbbfc57af4b5 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RemoteDiagnosticListTable.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteDiagnosticListTable.cs @@ -30,8 +30,8 @@ internal class RemoteDiagnosticListTable : VisualStudioBaseDiagnosticListTable [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Incorrectly used in production code: https://github.com/dotnet/roslyn/issues/42839")] public RemoteDiagnosticListTable( - SVsServiceProvider serviceProvider, RemoteLanguageServiceWorkspace workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider) : - base(workspace, provider) + SVsServiceProvider serviceProvider, RemoteLanguageServiceWorkspace workspace, IDiagnosticService diagnosticService, ITableManagerProvider provider) + : base(workspace, provider) { _source = new LiveTableDataSource(workspace, diagnosticService, IdentifierString); AddInitialTableSource(workspace.CurrentSolution, _source); @@ -54,6 +54,7 @@ protected override void AddTableSourceIfNecessary(Solution solution) { return; } + AddTableSource(_source); } diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs index 829283c6afaeb..ca25e602e59a4 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs @@ -386,6 +386,7 @@ public override void OpenDocument(DocumentId documentId, bool activate = true) { return; } + _threadingContext.JoinableTaskFactory.Run(async () => { await _session.DownloadFileAsync(_session.ConvertLocalPathToSharedUri(doc.FilePath), CancellationToken.None).ConfigureAwait(true); diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspaceHost.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspaceHost.cs index b9b56e88512cd..5a172d35bea0d 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspaceHost.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspaceHost.cs @@ -159,6 +159,7 @@ private void CloseAllProjects() { _remoteLanguageServiceWorkspace.OnProjectRemoved(projectId); } + _loadedProjects = _loadedProjects.Clear(); _loadedProjectInfo = _loadedProjectInfo.Clear(); } diff --git a/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockTextPoint.vb b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockTextPoint.vb index 19a3eee5cccf0..1826f5cdd35f4 100644 --- a/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockTextPoint.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockTextPoint.vb @@ -111,6 +111,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks If _point.IsInVirtualSpace Then result += _point.VirtualSpaces End If + Return result End Get End Property diff --git a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.NodeNameGenerator.vb b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.NodeNameGenerator.vb index 15a8fc8050c64..0cac9701d4650 100644 --- a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.NodeNameGenerator.vb +++ b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.NodeNameGenerator.vb @@ -173,6 +173,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeModel If methodStatement.TypeParameterList IsNot Nothing Then AppendArity(builder, methodStatement.TypeParameterList.Parameters.Count) End If + AppendParameterList(builder, methodStatement.ParameterList) Case SyntaxKind.FunctionStatement, @@ -183,6 +184,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeModel If methodStatement.TypeParameterList IsNot Nothing Then AppendArity(builder, methodStatement.TypeParameterList.Parameters.Count) End If + AppendParameterList(builder, methodStatement.ParameterList) Case SyntaxKind.DeclareFunctionStatement, @@ -204,6 +206,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeModel builder.Append("_"c) AppendTypeName(builder, DirectCast(operatorStatement.AsClause, SimpleAsClauseSyntax).Type) End If + AppendParameterList(builder, operatorStatement.ParameterList) Case SyntaxKind.ConstructorBlock diff --git a/src/VisualStudio/VisualBasic/Impl/Help/VisualBasicHelpContextService.Visitor.vb b/src/VisualStudio/VisualBasic/Impl/Help/VisualBasicHelpContextService.Visitor.vb index 3c7b4c3e60236..0e132cd6942f4 100644 --- a/src/VisualStudio/VisualBasic/Impl/Help/VisualBasicHelpContextService.Visitor.vb +++ b/src/VisualStudio/VisualBasic/Impl/Help/VisualBasicHelpContextService.Visitor.vb @@ -110,6 +110,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Help If Not node.IntoKeyword.IsMissing Then result = HelpKeywords.QueryAggregateInto End If + result = HelpKeywords.QueryAggregate End Sub diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb index d1df51e52f392..850c805e40da7 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb @@ -1,4 +1,4 @@ -' Licensed to the .NET Foundation under one or more agreements. +' Licensed to the .NET Foundation under one or more agreements. ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. @@ -23,4 +23,4 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeCleanup MyBase.New(codeCleanUpFixers) End Sub End Class -End Namespace \ No newline at end of file +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml index bf0bfc8251a35..4d6d131fb3d37 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml @@ -34,6 +34,8 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_Show_Remove_Unused_References_command_in_Solution_Explorer_experimental}" /> + 0) + End Set + End Property End Class End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetExpansionClient.vb b/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetExpansionClient.vb index 234ddd3b70249..db67e096800da 100644 --- a/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetExpansionClient.vb +++ b/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetExpansionClient.vb @@ -229,6 +229,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Snippets ordinalIgnoreCaseStringComparer.Equals(x.XmlNamespace.Value.ToString(), xmlNamespaceImportsClause.XmlNamespace.Value.ToString())) Then uniqueClauses.Add(clause) End If + Continue For End If Next diff --git a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx index a69f19d3134c8..e75e5a640b154 100644 --- a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx +++ b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx @@ -128,7 +128,7 @@ Visual Basic Editor - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf index f1301cc8ea921..c43220b35bb48 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Zobrazovat vložené nápovědy;Automatické vložení koncových konstruktorů;Změnit nastavení přehledného výpisu;Změnit režim sbalení;Automatické vkládání členů Interface a MustOverride;Zobrazit nebo skrýt oddělovače řádků procedur;Zapnout nebo vypnout návrhy oprav;Zapnout nebo vypnout zvýrazňování odkazů a klíčových slov;Regex;Obarvit regulární výrazy;Zvýrazňovat související komponenty pod kurzorem;Nahlásit neplatné regulární výrazy;reg. výr.;regulární výraz;Používat rozšířené barvy;Barevné schéma editoru; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf index f545b1377989f..72eb3a80f58fd 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Inline-Hinweise anzeigen;Endkonstrukte automatisch einfügen;Einstellungen für automatische Strukturierung und Einrückung ändern;Gliederungsmodus ändern;Schnittstellen- und MustOverride-Member automatisch einfügen;Zeilentrennzeichen zwischen Prozeduren anzeigen oder ausblenden;Vorschläge für Fehlerkorrektur ein- oder ausschalten;Hervorhebung von Verweisen und Schlüsselwörtern ein- oder ausschalten;Regex;Reguläre Ausdrücke farbig markieren;Verwandte Komponenten unter Cursor hervorheben;Ungültige reguläre Ausdrücke melden;regex;regulärer Ausdruck;nErweiterte Farben verwenden;Editor-Farbschema; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf index 65ad78eb34fa0..4774410bcdb3d 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Mostrar sugerencias insertadas;Inserción automática de construcciones End;Cambiar configuración de la lista descriptiva;Cambiar modo de esquematización;Inserción automática de miembros Interface y MustOverride;Mostrar u ocultar separadores de línea de procedimientos;Activar o desactivar sugerencias de corrección de errores;Activar o desactivar resaltado de referencias y palabras clave;Regex;Colorear expresiones regulares;Resaltar componentes relacionados bajo el cursor;Informar sobre expresiones regulares no válidas;regex;expresión regular;Usar colores mejorados;Combinación de colores del editor; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf index 55d1ccfdb9da3..3743b36090c47 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Afficher les indicateurs inline;Insertion automatique des constructions de fin;Changer les paramètres de listing en mode Pretty;Changer le mode Plan;Insertion automatique des membres Interface et MustOverride;Afficher ou masquer les séparateurs de ligne de procédure;Activer ou désactiver les suggestions de correction d'erreurs;Activer ou désactiver la mise en surbrillance des références et des mots clés;Regex;Mettre en couleurs les expressions régulières;Mettre en surbrillance les composants connexes sous le curseur;Signaler les expressions régulières non valides;regex;expression régulière;Utiliser des couleurs améliorées;Modèle de couleurs de l'éditeur; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf index a5f8592425d0d..0bb83c019b3e9 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Visualizza suggerimenti inline;Inserimento automatico di costrutti End;Modifica impostazioni di riformattazione;Modifica modalità struttura;Inserimento automatico di membri Interface e MustOverride;Mostra o nascondi separatori di riga routine;Attiva o disattiva i suggerimenti per la correzione degli errori;Attiva o disattiva l'evidenziazione di riferimenti e parole chiave;Regex;Colora espressioni regolari;Evidenzia i componenti correlati sotto il cursore;Segnala espressioni regolari non valide;regex;espressione regolare;Usa colori migliorati;Combinazione colori editor; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf index ef4ff8eaf0481..a9e2a1ad494ea 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; インラインのヒントを表示する;コンストラクトの終端の自動挿入;再フォーマット設定の変更;アウトライン モードの変更;Interface と MustOverride メンバーの自動挿入;プロシージャ行の区切り記号の表示/非表示;エラー修正候補のオン/オフの切り替え;参照とキーワードの強調表示のオン/オフの切り替え;Regex;正規表現の色付け;カーソルの下の関連コンポーネントの強調表示;無効な正規表現の報告;Regex;正規表現;拡張された色を使用する;エディターの配色; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf index 995463caa041e..ff9afb9de86da 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; 인라인 힌트 표시; 맺음 구문 자동 삽입; 자동 서식 지정 설정 변경; 개요 모드 변경; 인터페이스 및 MustOverride 멤버 자동 삽입; 프로시저 줄 구분 기호 표시/숨기기; 오류 수정 제안 설정/해제; 참조 및 키워드 강조 표시 설정/해제; Regex; 정규식 색 지정; 커서 아래의 관련 구성 요소 강조 표시; 잘못된 정규식 보고; regex; 정규식; 향상된 색 사용; 편집기 색 구성표; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf index 4d3ce46f9c81d..454538178e9a0 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Wyświetlaj wskazówki w tekście;Automatyczne wstawianie konstrukcji końcowych;Zmień ustawienia formatowania kodu;Zmień tryb konspektu;Automatyczne wstawianie składowych Interface i MustOverride;Pokaż lub ukryj separatory wierszy procedury;Włącz lub wyłącz sugestie dotyczące poprawy błędów;Włącz lub wyłącz wyróżnianie odwołań i słów kluczowych;Wyrażenie regularne;Koloruj wyrażenia regularne;Wyróżnij pokrewne składniki wskazane przez kursor;Zgłaszaj nieprawidłowe wyrażenia regularne;wyrażenie regularne;wyrażenie regularne;Użyj ulepszonych kolorów;Schemat kolorów edytora; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf index 4b0ae56e9e10a..fa9542d479d66 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Exibir as dicas embutidas;Inserção automática de constructos finais;Alterar as configurações de reformatação automática;Alterar o modo de estrutura de tópicos;Inserção automática dos membros Interface e MustOverride;Mostrar ou ocultar os separadores de linha de procedimento;Ativar ou desativar as sugestões para correção de erros;Ativar ou desativar o realce de referências e palavras-chave;Regex;Colorir as expressões regulares;Realçar os componentes relacionados sob o cursor;Relatar as expressões regulares inválidas;regex;expressão regular;Usar cores avançadas;Esquema de Cores do Editor; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf index e2c1bf83adf60..145b6d4db2350 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Отображать встроенные подсказки;Автоматическая вставка конечных конструкций;Изменение параметров автоматического форматирования;Изменение режима структуры;Автоматическая вставка интерфейса и элементов MustOverride;Отображение или скрытие разделителей строк для процедуры;Включение или отключение предложений об исправлении ошибок;Включение или отключение выделения ссылок и ключевых слов;Регулярные выражения;Выделение регулярных выражений цветом;Выделение связанных компонентов под курсором;Выделение недопустимых регулярных выражений;регулярное выражение;регулярное выражение;Использование расширенных цветов;Цветовая схема редактора; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf index 602652766f332..4c2ed918a7510 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; Satır içi ipuçlarını göster;Bitiş yapılarının otomatik olarak eklenmesi;Düzgün listeleme ayarlarını değiştir;Ana hat modunu değiştir;Interface ve MustOverride üyelerinin otomatik olarak eklenmesi;Yordam satır ayıraçlarını göster veya gizle;Hata düzeltme önerilerini aç veya kapat;Başvuruları ve anahtar sözcükleri vurgulamayı aç veya kapat;Normal ifade;Normal ifadeleri renklendir;İmlecin altındaki ilgili bileşenleri vurgula;Geçersiz normal ifadeleri bildir;normal ifade;normal ifade;Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf index da83604bde627..bd6c97c23ba89 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; 显示内联提示;自动插入 End 构造;更改整齐排列设置;更改大纲模式;自动插入 Interface 和 MustOverride 成员;显示或隐藏过程行分隔符;打开或关闭错误纠正建议;打开或关闭引用和关键字的突出显示;正则表达式;对正则表达式着色;突出显示光标下的相关部分;报告无效正则表达式;regex;正则表达式;使用增强色;编辑器配色方案; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf index dc7fa458309bf..906a7cf8f668a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf @@ -18,7 +18,7 @@ - Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme;Inheritance Margin; 顯示內嵌提示;自動插入 End 建構;變更美化列表設定;變更大綱模式;自動插入 Interface 和 MustOverride 成員;顯示或隱藏程序行分隔符號;開啟或關閉錯誤修正建議;開啟或關閉參考及關鍵字的醒目提示;Regex;為規則運算式著色;醒目提示游標下的相關元件;回報無效的規則運算式;regex;規則運算式;使用進階色彩;編輯器色彩配置; Advanced options page keywords diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs index ad73ad856d6f6..1cc5c40758609 100644 --- a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs @@ -27,5 +27,6 @@ internal class XamlCompletionItem public ISymbol Symbol { get; set; } public XamlEventDescription? EventDescription { get; set; } public bool RetriggerCompletion { get; set; } + public bool IsSnippet { get; set; } } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs index 963f50ff8340e..e33d35d4b7478 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs @@ -46,6 +46,7 @@ public XamlInProcLanguageClient( /// /// Gets the name of the language client (displayed in yellow bars). + /// When updating the string of Name, please make sure to update the same string in Microsoft.VisualStudio.LanguageServer.Client.ExperimentalSnippetSupport.AllowList /// public override string Name => "XAML Language Server Client (Experimental)"; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Extensions/SymbolExtensions.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Extensions/SymbolExtensions.cs index b183d296c2521..1ce359f667596 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Extensions/SymbolExtensions.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Extensions/SymbolExtensions.cs @@ -62,8 +62,10 @@ public static async Task> GetDescriptionAsync(this ISymb { builder.AddLineBreak(); } + builder.AddRange(section.TaggedParts); } + return builder.ToImmutableArray(); } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs index 99027afb1a00b..444f6c1d4cb5d 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs @@ -93,6 +93,7 @@ private static CompletionItem CreateCompletionItem(XamlCompletionItem xamlComple Kind = GetItemKind(xamlCompletion.Kind), Description = xamlCompletion.Description, Icon = xamlCompletion.Icon, + InsertTextFormat = xamlCompletion.IsSnippet ? InsertTextFormat.Snippet : InsertTextFormat.Plaintext, Data = new CompletionResolveData { ProjectGuid = documentId.ProjectId.Id, DocumentGuid = documentId.Id, Position = position, DisplayText = xamlCompletion.DisplayText } }; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs index 98f87af0d5eac..a97a6e68b2293 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs @@ -70,6 +70,7 @@ public HoverHandler() { descriptionBuilder.AddLineBreak(); } + descriptionBuilder.AddRange(description); } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs b/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs index 6ee869523c794..4e2281b555d67 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs @@ -80,6 +80,7 @@ public XamlProjectService( { _documentIds.TryAdd(filePath, documentId); } + return documentId; DocumentId? GetDocumentId(string path) @@ -202,6 +203,7 @@ private void OnDocumentClosed(object sender, DocumentEventArgs e) var project = _xamlProjects.Values.SingleOrDefault(p => p.Id == document.Project.Id); project?.RemoveSourceFile(document.FilePath); } + _documentIds.TryRemove(filePath, out _); } } diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs index 91be6c144caa2..23493dc55e668 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs @@ -42,6 +42,7 @@ public override void AddClassifications( { result.Add(new ClassifiedSpan(parameter.Identifier.Span, ClassificationTypeNames.Keyword)); } + break; case IdentifierNameSyntax identifierName when identifierName.Identifier.Text == "_": @@ -51,6 +52,7 @@ public override void AddClassifications( { result.Add(new ClassifiedSpan(syntax.Span, ClassificationTypeNames.Keyword)); } + break; } } diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs index badd7591a6ec2..f236e3444876a 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs @@ -212,6 +212,7 @@ private static bool TryClassifySymbol( classifiedSpan = new ClassifiedSpan(token.Span, classification); return true; } + break; case IFieldSymbol fieldSymbol: diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index b2f0ab1bfc4b1..ceaff08c190dc 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -854,6 +854,7 @@ private EnumMemberDeclarationSyntax AsEnumMember(SyntaxNode node) var vd = fd.Declaration.Variables[0]; return (EnumMemberDeclarationSyntax)this.EnumMember(vd.Identifier.ToString(), vd.Initializer?.Value); } + break; } @@ -1067,6 +1068,7 @@ public override IReadOnlyList GetAttributeArguments(SyntaxNode attri { return this.GetAttributeArguments(list.Attributes[0]); } + break; case SyntaxKind.Attribute: var attr = (AttributeSyntax)attributeDeclaration; @@ -1074,6 +1076,7 @@ public override IReadOnlyList GetAttributeArguments(SyntaxNode attri { return attr.ArgumentList.Arguments; } + break; } @@ -1113,6 +1116,7 @@ private static AttributeArgumentListSyntax GetAttributeArgumentList(SyntaxNode d { return list.Attributes[0].ArgumentList; } + break; case SyntaxKind.Attribute: var attr = (AttributeSyntax)declaration; @@ -1132,6 +1136,7 @@ private static SyntaxNode WithAttributeArgumentList(SyntaxNode declaration, Attr { return ReplaceWithTrivia(declaration, list.Attributes[0], list.Attributes[0].WithArgumentList(argList)); } + break; case SyntaxKind.Attribute: var attr = (AttributeSyntax)declaration; @@ -1240,6 +1245,7 @@ private static ImmutableArray Flatten(IEnumerable declar { builder.Add(attrList); } + break; default: @@ -1832,6 +1838,7 @@ public override SyntaxNode GetType(SyntaxNode declaration) { return this.GetType(declaration.Parent); } + break; } @@ -1879,6 +1886,7 @@ private SyntaxNode AsIsolatedDeclaration(SyntaxNode declaration) { return this.AsIsolatedDeclaration(vd.Parent); } + break; case SyntaxKind.VariableDeclarator: @@ -1887,6 +1895,7 @@ private SyntaxNode AsIsolatedDeclaration(SyntaxNode declaration) { return this.ClearTrivia(WithVariable(v.Parent.Parent, v)); } + break; case SyntaxKind.Attribute: @@ -1896,6 +1905,7 @@ private SyntaxNode AsIsolatedDeclaration(SyntaxNode declaration) var attrList = (AttributeListSyntax)attr.Parent; return attrList.WithAttributes(SyntaxFactory.SingletonSeparatedList(attr)).WithTarget(null); } + break; } } @@ -1955,6 +1965,7 @@ private static SyntaxNode GetFullDeclaration(SyntaxNode declaration) { return GetFullDeclaration(declaration.Parent); } + break; } @@ -1976,6 +1987,7 @@ private SyntaxNode AsNodeLike(SyntaxNode existingNode, SyntaxNode newNode) { return this.AsMemberOf(container, newNode); } + break; case DeclarationKind.Attribute: @@ -2104,6 +2116,9 @@ private static SyntaxNode WithParameterList(SyntaxNode declaration, BaseParamete .WithLeadingTrivia(lambda.GetLeadingTrivia()) .WithTrailingTrivia(lambda.GetTrailingTrivia()); } + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: + return ((RecordDeclarationSyntax)declaration).WithParameterList((ParameterListSyntax)list); default: return declaration; } @@ -2124,6 +2139,7 @@ public override SyntaxNode GetExpression(SyntaxNode declaration) { return pd.ExpressionBody.Expression; } + goto default; case SyntaxKind.IndexerDeclaration: @@ -2132,6 +2148,7 @@ public override SyntaxNode GetExpression(SyntaxNode declaration) { return id.ExpressionBody.Expression; } + goto default; case SyntaxKind.MethodDeclaration: @@ -2140,6 +2157,7 @@ public override SyntaxNode GetExpression(SyntaxNode declaration) { return method.ExpressionBody.Expression; } + goto default; case SyntaxKind.LocalFunctionStatement: @@ -2148,6 +2166,7 @@ public override SyntaxNode GetExpression(SyntaxNode declaration) { return local.ExpressionBody.Expression; } + goto default; default: @@ -2176,6 +2195,7 @@ private static SyntaxNode WithExpressionInternal(SyntaxNode declaration, SyntaxN { return ReplaceWithTrivia(pd, pd.ExpressionBody.Expression, expr); } + goto default; case SyntaxKind.IndexerDeclaration: @@ -2184,6 +2204,7 @@ private static SyntaxNode WithExpressionInternal(SyntaxNode declaration, SyntaxN { return ReplaceWithTrivia(id, id.ExpressionBody.Expression, expr); } + goto default; case SyntaxKind.MethodDeclaration: @@ -2192,6 +2213,7 @@ private static SyntaxNode WithExpressionInternal(SyntaxNode declaration, SyntaxN { return ReplaceWithTrivia(method, method.ExpressionBody.Expression, expr); } + goto default; case SyntaxKind.LocalFunctionStatement: @@ -2200,6 +2222,7 @@ private static SyntaxNode WithExpressionInternal(SyntaxNode declaration, SyntaxN { return ReplaceWithTrivia(local, local.ExpressionBody.Expression, expr); } + goto default; default: @@ -2237,6 +2260,7 @@ private static EqualsValueClauseSyntax GetEqualsValue(SyntaxNode declaration) { return fd.Declaration.Variables[0].Initializer; } + break; case SyntaxKind.PropertyDeclaration: var pd = (PropertyDeclarationSyntax)declaration; @@ -2247,6 +2271,7 @@ private static EqualsValueClauseSyntax GetEqualsValue(SyntaxNode declaration) { return ld.Declaration.Variables[0].Initializer; } + break; case SyntaxKind.VariableDeclaration: var vd = (VariableDeclarationSyntax)declaration; @@ -2254,6 +2279,7 @@ private static EqualsValueClauseSyntax GetEqualsValue(SyntaxNode declaration) { return vd.Variables[0].Initializer; } + break; case SyntaxKind.VariableDeclarator: return ((VariableDeclaratorSyntax)declaration).Initializer; @@ -2274,6 +2300,7 @@ private static SyntaxNode WithEqualsValue(SyntaxNode declaration, EqualsValueCla { return ReplaceWithTrivia(declaration, fd.Declaration.Variables[0], fd.Declaration.Variables[0].WithInitializer(eq)); } + break; case SyntaxKind.PropertyDeclaration: var pd = (PropertyDeclarationSyntax)declaration; @@ -2284,6 +2311,7 @@ private static SyntaxNode WithEqualsValue(SyntaxNode declaration, EqualsValueCla { return ReplaceWithTrivia(declaration, ld.Declaration.Variables[0], ld.Declaration.Variables[0].WithInitializer(eq)); } + break; case SyntaxKind.VariableDeclaration: var vd = (VariableDeclarationSyntax)declaration; @@ -2291,6 +2319,7 @@ private static SyntaxNode WithEqualsValue(SyntaxNode declaration, EqualsValueCla { return ReplaceWithTrivia(declaration, vd.Variables[0], vd.Variables[0].WithInitializer(eq)); } + break; case SyntaxKind.VariableDeclarator: return ((VariableDeclaratorSyntax)declaration).WithInitializer(eq); @@ -2435,6 +2464,7 @@ private static AccessorDeclarationSyntax AsAccessor(SyntaxNode node, SyntaxKind case SyntaxKind.SetAccessorDeclaration: return (AccessorDeclarationSyntax)node; } + break; case SyntaxKind.EventDeclaration: switch (node.Kind()) @@ -2443,6 +2473,7 @@ private static AccessorDeclarationSyntax AsAccessor(SyntaxNode node, SyntaxKind case SyntaxKind.RemoveAccessorDeclaration: return (AccessorDeclarationSyntax)node; } + break; } @@ -2846,6 +2877,7 @@ private SyntaxNode RemoveNodeInternal(SyntaxNode root, SyntaxNode declaration, S // remove entire list if only one attribute return this.RemoveNodeInternal(root, attrList, options); } + break; case SyntaxKind.AttributeArgument: @@ -2854,6 +2886,7 @@ private SyntaxNode RemoveNodeInternal(SyntaxNode root, SyntaxNode declaration, S // remove entire argument list if only one argument return this.RemoveNodeInternal(root, declaration.Parent, options); } + break; case SyntaxKind.VariableDeclarator: @@ -2863,6 +2896,7 @@ private SyntaxNode RemoveNodeInternal(SyntaxNode root, SyntaxNode declaration, S // remove full declaration if only one declarator return this.RemoveNodeInternal(root, full, options); } + break; case SyntaxKind.SimpleBaseType: @@ -2871,6 +2905,7 @@ private SyntaxNode RemoveNodeInternal(SyntaxNode root, SyntaxNode declaration, S // remove entire base list if this is the only base type. return this.RemoveNodeInternal(root, baseList, options); } + break; default: @@ -2883,6 +2918,7 @@ private SyntaxNode RemoveNodeInternal(SyntaxNode root, SyntaxNode declaration, S return this.RemoveNodeInternal(root, parent, options); } } + break; } diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/MethodGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/MethodGenerator.cs index bf8f904398b2b..451fb52bf6cdf 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/MethodGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/MethodGenerator.cs @@ -220,9 +220,14 @@ private static SyntaxTokenList GenerateModifiers( { var tokens = ArrayBuilder.GetInstance(); - // Only "unsafe" modifier allowed if we're an explicit impl. + // Only "static" and "unsafe" modifiers allowed if we're an explicit impl. if (method.ExplicitInterfaceImplementations.Any()) { + if (method.IsStatic) + { + tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + } + if (CodeGenerationMethodInfo.GetIsUnsafe(method)) { tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); @@ -237,6 +242,11 @@ private static SyntaxTokenList GenerateModifiers( { AddAccessibilityModifiers(method.DeclaredAccessibility, tokens, options, Accessibility.Private); + if (method.IsStatic) + { + tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + } + if (method.IsAbstract) { tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); @@ -247,11 +257,6 @@ private static SyntaxTokenList GenerateModifiers( tokens.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); } - if (method.IsStatic) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - } - // Don't show the readonly modifier if the containing type is already readonly // ContainingSymbol is used to guard against methods which are not members of their ContainingType (e.g. lambdas and local functions) if (method.IsReadOnly && (method.ContainingSymbol as INamedTypeSymbol)?.IsReadOnly != true) diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs index 819a5ab6d9bed..570cdbf4041d7 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs @@ -10,7 +10,8 @@ using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; - +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Extensions; using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; @@ -74,7 +75,7 @@ private static OperatorDeclarationSyntax GenerateOperatorDeclarationWorker( CodeGenerationOptions options, ParseOptions parseOptions) { - var hasNoBody = !options.GenerateMethodBodies || method.IsExtern; + var hasNoBody = !options.GenerateMethodBodies || method.IsExtern || method.IsAbstract; var operatorSyntaxKind = SyntaxFacts.GetOperatorKind(method.MetadataName); if (operatorSyntaxKind == SyntaxKind.None) @@ -86,23 +87,37 @@ private static OperatorDeclarationSyntax GenerateOperatorDeclarationWorker( var operatorDecl = SyntaxFactory.OperatorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), options), - modifiers: GenerateModifiers(), + modifiers: GenerateModifiers(method), returnType: method.ReturnType.GenerateTypeSyntax(), + explicitInterfaceSpecifier: GenerateExplicitInterfaceSpecifier(method.ExplicitInterfaceImplementations), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), operatorToken: operatorToken, parameterList: ParameterGenerator.GenerateParameterList(method.Parameters, isExplicit: false, options: options), body: hasNoBody ? null : StatementGenerator.GenerateBlock(method), + expressionBody: null, semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : new SyntaxToken()); operatorDecl = UseExpressionBodyIfDesired(options, operatorDecl, parseOptions); return operatorDecl; } - private static SyntaxTokenList GenerateModifiers() + private static SyntaxTokenList GenerateModifiers(IMethodSymbol method) { - return SyntaxFactory.TokenList( - SyntaxFactory.Token(SyntaxKind.PublicKeyword), - SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + using var tokens = TemporaryArray.Empty; + + if (method.ExplicitInterfaceImplementations.Length == 0) + { + tokens.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + } + + tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + + if (method.IsAbstract) + { + tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + } + + return tokens.ToImmutableAndClear().ToSyntaxTokenList(); } } } diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs index 73116e2bd4528..040d4ad5fef1f 100644 --- a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using System.Linq; using System.Text; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageServices; @@ -24,7 +25,13 @@ namespace Microsoft.CodeAnalysis.CSharp.FindSymbols { [ExportLanguageService(typeof(IDeclaredSymbolInfoFactoryService), LanguageNames.CSharp), Shared] - internal class CSharpDeclaredSymbolInfoFactoryService : AbstractDeclaredSymbolInfoFactoryService + internal class CSharpDeclaredSymbolInfoFactoryService : AbstractDeclaredSymbolInfoFactoryService< + CompilationUnitSyntax, + UsingDirectiveSyntax, + NamespaceDeclarationSyntax, + TypeDeclarationSyntax, + EnumDeclarationSyntax, + MemberDeclarationSyntax> { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -147,7 +154,16 @@ private static void AddInheritanceName( } } - public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNode node, string rootNamespace, out DeclaredSymbolInfo declaredSymbolInfo) + protected override void AddDeclaredSymbolInfosWorker( + SyntaxNode container, + MemberDeclarationSyntax node, + StringTable stringTable, + ArrayBuilder declaredSymbolInfos, + Dictionary aliases, + Dictionary> extensionMethodInfo, + string containerDisplayName, + string fullyQualifiedContainerName, + CancellationToken cancellationToken) { // If this is a part of partial type that only contains nested types, then we don't make an info type for // it. That's because we effectively think of this as just being a virtual container just to hold the nested @@ -158,8 +174,7 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod typeDeclaration.Members.Any() && typeDeclaration.Members.All(m => m is BaseTypeDeclarationSyntax)) { - declaredSymbolInfo = default; - return false; + return; } switch (node.Kind()) @@ -170,12 +185,12 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: var typeDecl = (TypeDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, typeDecl.Identifier.ValueText, GetTypeParameterSuffix(typeDecl.TypeParameterList), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword), node.Kind() switch { @@ -186,126 +201,128 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod SyntaxKind.RecordStructDeclaration => DeclaredSymbolInfoKind.RecordStruct, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), }, - GetAccessibility(typeDecl, typeDecl.Modifiers), + GetAccessibility(container, typeDecl.Modifiers), typeDecl.Identifier.Span, GetInheritanceNames(stringTable, typeDecl.BaseList), - IsNestedType(typeDecl)); - return true; + IsNestedType(typeDecl))); + return; case SyntaxKind.EnumDeclaration: var enumDecl = (EnumDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, enumDecl.Identifier.ValueText, null, - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, enumDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Enum, - GetAccessibility(enumDecl, enumDecl.Modifiers), + GetAccessibility(container, enumDecl.Modifiers), enumDecl.Identifier.Span, inheritanceNames: ImmutableArray.Empty, - isNestedType: IsNestedType(enumDecl)); - return true; + isNestedType: IsNestedType(enumDecl))); + return; case SyntaxKind.ConstructorDeclaration: var ctorDecl = (ConstructorDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, ctorDecl.Identifier.ValueText, GetConstructorSuffix(ctorDecl), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, ctorDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Constructor, - GetAccessibility(ctorDecl, ctorDecl.Modifiers), + GetAccessibility(container, ctorDecl.Modifiers), ctorDecl.Identifier.Span, inheritanceNames: ImmutableArray.Empty, - parameterCount: ctorDecl.ParameterList?.Parameters.Count ?? 0); - return true; + parameterCount: ctorDecl.ParameterList?.Parameters.Count ?? 0)); + return; case SyntaxKind.DelegateDeclaration: var delegateDecl = (DelegateDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, delegateDecl.Identifier.ValueText, GetTypeParameterSuffix(delegateDecl.TypeParameterList), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, delegateDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Delegate, - GetAccessibility(delegateDecl, delegateDecl.Modifiers), + GetAccessibility(container, delegateDecl.Modifiers), delegateDecl.Identifier.Span, - inheritanceNames: ImmutableArray.Empty); - return true; + inheritanceNames: ImmutableArray.Empty)); + return; case SyntaxKind.EnumMemberDeclaration: var enumMember = (EnumMemberDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, enumMember.Identifier.ValueText, null, - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, enumMember.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.EnumMember, Accessibility.Public, enumMember.Identifier.Span, - inheritanceNames: ImmutableArray.Empty); - return true; + inheritanceNames: ImmutableArray.Empty)); + return; case SyntaxKind.EventDeclaration: var eventDecl = (EventDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, eventDecl.Identifier.ValueText, null, - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, eventDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Event, - GetAccessibility(eventDecl, eventDecl.Modifiers), + GetAccessibility(container, eventDecl.Modifiers), eventDecl.Identifier.Span, - inheritanceNames: ImmutableArray.Empty); - return true; + inheritanceNames: ImmutableArray.Empty)); + return; case SyntaxKind.IndexerDeclaration: var indexerDecl = (IndexerDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, "this", GetIndexerSuffix(indexerDecl), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, indexerDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Indexer, - GetAccessibility(indexerDecl, indexerDecl.Modifiers), + GetAccessibility(container, indexerDecl.Modifiers), indexerDecl.ThisKeyword.Span, - inheritanceNames: ImmutableArray.Empty); - return true; + inheritanceNames: ImmutableArray.Empty)); + return; case SyntaxKind.MethodDeclaration: var method = (MethodDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + var isExtensionMethod = IsExtensionMethod(method); + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, method.Identifier.ValueText, GetMethodSuffix(method), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, method.Modifiers.Any(SyntaxKind.PartialKeyword), - IsExtensionMethod(method) ? DeclaredSymbolInfoKind.ExtensionMethod : DeclaredSymbolInfoKind.Method, - GetAccessibility(method, method.Modifiers), + isExtensionMethod ? DeclaredSymbolInfoKind.ExtensionMethod : DeclaredSymbolInfoKind.Method, + GetAccessibility(container, method.Modifiers), method.Identifier.Span, inheritanceNames: ImmutableArray.Empty, parameterCount: method.ParameterList?.Parameters.Count ?? 0, - typeParameterCount: method.TypeParameterList?.Parameters.Count ?? 0); - return true; + typeParameterCount: method.TypeParameterList?.Parameters.Count ?? 0)); + if (isExtensionMethod) + AddExtensionMethodInfo(method, aliases, declaredSymbolInfos.Count - 1, extensionMethodInfo); + return; case SyntaxKind.PropertyDeclaration: var property = (PropertyDeclarationSyntax)node; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, property.Identifier.ValueText, null, - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent), + containerDisplayName, + fullyQualifiedContainerName, property.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Property, - GetAccessibility(property, property.Modifiers), + GetAccessibility(container, property.Modifiers), property.Identifier.Span, - inheritanceNames: ImmutableArray.Empty); - return true; - case SyntaxKind.VariableDeclarator: - // could either be part of a field declaration or an event field declaration - var variableDeclarator = (VariableDeclaratorSyntax)node; - var variableDeclaration = variableDeclarator.Parent as VariableDeclarationSyntax; - if (variableDeclaration?.Parent is BaseFieldDeclarationSyntax fieldDeclaration) + inheritanceNames: ImmutableArray.Empty)); + return; + case SyntaxKind.FieldDeclaration: + case SyntaxKind.EventFieldDeclaration: + var fieldDeclaration = (BaseFieldDeclarationSyntax)node; + foreach (var variableDeclarator in fieldDeclaration.Declaration.Variables) { var kind = fieldDeclaration is EventFieldDeclarationSyntax ? DeclaredSymbolInfoKind.Event @@ -313,26 +330,40 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod ? DeclaredSymbolInfoKind.Constant : DeclaredSymbolInfoKind.Field; - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, variableDeclarator.Identifier.ValueText, null, - GetContainerDisplayName(fieldDeclaration.Parent), - GetFullyQualifiedContainerName(fieldDeclaration.Parent), + containerDisplayName, + fullyQualifiedContainerName, fieldDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword), kind, - GetAccessibility(fieldDeclaration, fieldDeclaration.Modifiers), + GetAccessibility(container, fieldDeclaration.Modifiers), variableDeclarator.Identifier.Span, - inheritanceNames: ImmutableArray.Empty); - return true; + inheritanceNames: ImmutableArray.Empty)); } - break; + return; } - - declaredSymbolInfo = default; - return false; } + protected override SyntaxList GetChildren(CompilationUnitSyntax node) + => node.Members; + + protected override SyntaxList GetChildren(NamespaceDeclarationSyntax node) + => node.Members; + + protected override SyntaxList GetChildren(TypeDeclarationSyntax node) + => node.Members; + + protected override IEnumerable GetChildren(EnumDeclarationSyntax node) + => node.Members; + + protected override SyntaxList GetUsingAliases(CompilationUnitSyntax node) + => node.Usings; + + protected override SyntaxList GetUsingAliases(NamespaceDeclarationSyntax node) + => node.Usings; + private static bool IsNestedType(BaseTypeDeclarationSyntax typeDecl) => typeDecl.Parent is BaseTypeDeclarationSyntax; @@ -432,13 +463,13 @@ private static void AppendParameters(SeparatedSyntaxList parame } } - private static string GetContainerDisplayName(SyntaxNode node) + protected override string GetContainerDisplayName(MemberDeclarationSyntax node) => CSharpSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeTypeParameters); - private static string GetFullyQualifiedContainerName(SyntaxNode node) + protected override string GetFullyQualifiedContainerName(MemberDeclarationSyntax node, string rootNamespace) => CSharpSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeNamespaces); - private static Accessibility GetAccessibility(SyntaxNode node, SyntaxTokenList modifiers) + private static Accessibility GetAccessibility(SyntaxNode container, SyntaxTokenList modifiers) { var sawInternal = false; foreach (var modifier in modifiers) @@ -455,12 +486,10 @@ private static Accessibility GetAccessibility(SyntaxNode node, SyntaxTokenList m } if (sawInternal) - { return Accessibility.Internal; - } // No accessibility modifiers: - switch (node.Parent.Kind()) + switch (container.Kind()) { case SyntaxKind.ClassDeclaration: case SyntaxKind.RecordDeclaration: @@ -473,10 +502,8 @@ private static Accessibility GetAccessibility(SyntaxNode node, SyntaxTokenList m return Accessibility.Public; case SyntaxKind.CompilationUnit: // Things are private by default in script - if (((CSharpParseOptions)node.SyntaxTree.Options).Kind == SourceCodeKind.Script) - { + if (((CSharpParseOptions)container.SyntaxTree.Options).Kind == SourceCodeKind.Script) return Accessibility.Private; - } return Accessibility.Internal; @@ -512,12 +539,13 @@ private static bool IsExtensionMethod(MethodDeclarationSyntax method) method.ParameterList.Parameters[0].Modifiers.Any(SyntaxKind.ThisKeyword); // Root namespace is a VB only concept, which basically means root namespace is always global in C#. - public override string GetRootNamespace(CompilationOptions compilationOptions) + protected override string GetRootNamespace(CompilationOptions compilationOptions) => string.Empty; - public override bool TryGetAliasesFromUsingDirective(SyntaxNode node, out ImmutableArray<(string aliasName, string name)> aliases) + protected override bool TryGetAliasesFromUsingDirective( + UsingDirectiveSyntax usingDirectiveNode, out ImmutableArray<(string aliasName, string name)> aliases) { - if (node is UsingDirectiveSyntax usingDirectiveNode && usingDirectiveNode.Alias != null) + if (usingDirectiveNode.Alias != null) { if (TryGetSimpleTypeName(usingDirectiveNode.Alias.Name, typeParameterNames: null, out var aliasName, out _) && TryGetSimpleTypeName(usingDirectiveNode.Name, typeParameterNames: null, out var name, out _)) @@ -531,7 +559,7 @@ public override bool TryGetAliasesFromUsingDirective(SyntaxNode node, out Immuta return false; } - public override string GetReceiverTypeName(SyntaxNode node) + protected override string GetReceiverTypeName(MemberDeclarationSyntax node) { var methodDeclaration = (MethodDeclarationSyntax)node; Debug.Assert(IsExtensionMethod(methodDeclaration)); @@ -562,7 +590,7 @@ private static bool TryGetSimpleTypeName(SyntaxNode node, ImmutableArray case GenericNameSyntax genericNameNode: var name = genericNameNode.Identifier.Text; var arity = genericNameNode.Arity; - simpleTypeName = arity == 0 ? name : name + GetMetadataAritySuffix(arity); + simpleTypeName = arity == 0 ? name : name + ArityUtilities.GetMetadataAritySuffix(arity); return true; case PredefinedTypeSyntax predefinedTypeNode: diff --git a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj index fad332e9ccc56..7f3239d0f5526 100644 --- a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj +++ b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj @@ -6,7 +6,7 @@ Microsoft.CodeAnalysis.CSharp true netcoreapp3.1;netstandard2.0 - partial + partial true diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs index c123818fa2c9e..7e11abc057cee 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs @@ -256,7 +256,7 @@ private RecommendedSymbols GetSymbolsOffOfName(NameSyntax name) } if (ShouldBeTreatedAsTypeInsteadOfExpression(name, out var nameBinding, out var container)) - return GetSymbolsOffOfBoundExpression(name, name, nameBinding, container); + return GetSymbolsOffOfBoundExpression(name, name, nameBinding, container, unwrapNullable: false); // We're in a name-only context, since if we were an expression we'd be a // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. @@ -353,14 +353,14 @@ private RecommendedSymbols GetSymbolsOffOfExpression(ExpressionSyntax? originalE var leftHandBinding = _context.SemanticModel.GetSymbolInfo(expression, _cancellationToken); var container = _context.SemanticModel.GetTypeInfo(expression, _cancellationToken).Type; - var result = GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); + var result = GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container, unwrapNullable: false); // Check for the Color Color case. if (originalExpression.CanAccessInstanceAndStaticMembersOffOf(_context.SemanticModel, _cancellationToken)) { var speculativeSymbolInfo = _context.SemanticModel.GetSpeculativeSymbolInfo(expression.SpanStart, expression, SpeculativeBindingOption.BindAsTypeOrNamespace); - var typeMembers = GetSymbolsOffOfBoundExpression(originalExpression, expression, speculativeSymbolInfo, container); + var typeMembers = GetSymbolsOffOfBoundExpression(originalExpression, expression, speculativeSymbolInfo, container, unwrapNullable: false); result = new RecommendedSymbols( result.NamedSymbols.Concat(typeMembers.NamedSymbols), @@ -381,7 +381,7 @@ private RecommendedSymbols GetSymbolsOffOfDereferencedExpression(ExpressionSynta container = pointerType.PointedAtType; } - return GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); + return GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container, unwrapNullable: false); } private RecommendedSymbols GetSymbolsOffOfConditionalReceiver(ExpressionSyntax originalExpression) @@ -392,22 +392,24 @@ private RecommendedSymbols GetSymbolsOffOfConditionalReceiver(ExpressionSyntax o var expression = originalExpression.WalkDownParentheses(); var leftHandBinding = _context.SemanticModel.GetSymbolInfo(expression, _cancellationToken); - var container = _context.SemanticModel.GetTypeInfo(expression, _cancellationToken).Type.RemoveNullableIfPresent(); + var container = _context.SemanticModel.GetTypeInfo(expression, _cancellationToken).Type; // If the thing on the left is a type, namespace, or alias, we shouldn't show anything in // IntelliSense. if (leftHandBinding.GetBestOrAllSymbols().FirstOrDefault().MatchesKind(SymbolKind.NamedType, SymbolKind.Namespace, SymbolKind.Alias)) return default; - return GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); + return GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container, unwrapNullable: true); } private RecommendedSymbols GetSymbolsOffOfBoundExpression( ExpressionSyntax originalExpression, ExpressionSyntax expression, SymbolInfo leftHandBinding, - ITypeSymbol? containerType) + ITypeSymbol? containerType, + bool unwrapNullable) { + var abstractsOnly = false; var excludeInstance = false; var excludeStatic = true; @@ -447,12 +449,14 @@ private RecommendedSymbols GetSymbolsOffOfBoundExpression( if (symbol is IAliasSymbol alias) symbol = alias.Target; - if (symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace) + if (symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.TypeParameter) { - // For named typed and namespaces, we flip things around. We only want statics and not instance members. + // For named typed, namespaces, and type parameters (potentially constrainted to interface with statics), we flip things around. + // We only want statics and not instance members. excludeInstance = true; excludeStatic = false; - containerSymbol = (INamespaceOrTypeSymbol)symbol; + abstractsOnly = symbol.Kind == SymbolKind.TypeParameter; + containerSymbol = symbol; } // Special case parameters. If we have a normal (non this/base) parameter, then that's what we want to @@ -479,6 +483,7 @@ private RecommendedSymbols GetSymbolsOffOfBoundExpression( return default; Debug.Assert(!excludeInstance || !excludeStatic); + Debug.Assert(!abstractsOnly || (abstractsOnly && !excludeStatic && excludeInstance)); // nameof(X.| // Show static and instance members. @@ -489,12 +494,12 @@ private RecommendedSymbols GetSymbolsOffOfBoundExpression( } var useBaseReferenceAccessibility = symbol is IParameterSymbol { IsThis: true } p && !p.Type.Equals(containerType); - var symbols = GetMemberSymbols(containerSymbol, position: originalExpression.SpanStart, excludeInstance, useBaseReferenceAccessibility); + var symbols = GetMemberSymbols(containerSymbol, position: originalExpression.SpanStart, excludeInstance, useBaseReferenceAccessibility, unwrapNullable); // If we're showing instance members, don't include nested types var namedSymbols = excludeStatic ? symbols.WhereAsArray(s => !(s.IsStatic || s is ITypeSymbol)) - : symbols; + : (abstractsOnly ? symbols.WhereAsArray(s => s.IsAbstract) : symbols); // if we're dotting off an instance, then add potential operators/indexers/conversions that may be // applicable to it as well. diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs index e8cf99794241c..bf8c370f9fb1e 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs @@ -308,6 +308,7 @@ private static bool CanMakeNameExplicitInTuple(TupleExpressionSyntax tuple, stri // No duplicate names allowed return false; } + found = true; } } @@ -502,6 +503,7 @@ private ExpressionSyntax VisitSimpleName(SimpleNameSyntax rewrittenSimpleName, S { replacement = replacement.ReplaceToken(firstOriginalToken, tokenWithLeadingWhitespace); } + break; case SyntaxKind.QualifiedName: diff --git a/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpCompilationFactoryService.cs b/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpCompilationFactoryService.cs index 00f5e22c59bb6..13d91d5fff3c6 100644 --- a/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpCompilationFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpCompilationFactoryService.cs @@ -41,7 +41,7 @@ Compilation ICompilationFactoryService.CreateSubmissionCompilation(string assemb CompilationOptions ICompilationFactoryService.GetDefaultCompilationOptions() => s_defaultOptions; - GeneratorDriver? ICompilationFactoryService.CreateGeneratorDriver(ParseOptions parseOptions, ImmutableArray generators, AnalyzerConfigOptionsProvider optionsProvider, ImmutableArray additionalTexts) + GeneratorDriver ICompilationFactoryService.CreateGeneratorDriver(ParseOptions parseOptions, ImmutableArray generators, AnalyzerConfigOptionsProvider optionsProvider, ImmutableArray additionalTexts) { return CSharpGeneratorDriver.Create(generators, additionalTexts, (CSharpParseOptions)parseOptions, optionsProvider); } diff --git a/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs index 3c7f46870cf5c..6cd7faf6edc63 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs @@ -51,6 +51,7 @@ private static async Task GetDocument(string code, bool withAnnotation }); doc = doc.WithSyntaxRoot(root); } + return doc; } diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index 68247fc533965..85ba35de37291 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -9028,6 +9028,7 @@ static string transform(string s) lines[i] = new string(' ', count: 8) + lines[i]; } } + return string.Join(Environment.NewLine, lines); } @@ -10021,5 +10022,82 @@ record struct R(int X); record struct R(int X); "); } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task LambdaReturnType_01() + { + await AssertFormatAsync( +@"class Program +{ + Delegate D = void () => { }; +}", +@"class Program +{ + Delegate D = void () => { }; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task LambdaReturnType_02() + { + await AssertFormatAsync( +@"class Program +{ + Delegate D = A.B () => { }; +}", +@"class Program +{ + Delegate D = A.B()=>{ }; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task LambdaReturnType_03() + { + await AssertFormatAsync( +@"class Program +{ + Delegate D = A (x) => x; +}", +@"class Program +{ + Delegate D = A < B > ( x ) => x; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task LambdaReturnType_04() + { + await AssertFormatAsync( +@"class Program +{ + object F = Func((A, B) ((A, B) t) => t); +}", +@"class Program +{ + object F = Func((A,B)((A,B)t)=>t); +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task LineSpanDirective() + { + var optionSet = new OptionsCollection(LanguageNames.CSharp) { { FormattingOptions2.UseTabs, true } }; + await AssertFormatAsync( +@"class Program +{ + static void Main() + { +#line (1, 1) - (1, 100) 5 ""a.razor"" + } +}", +@"class Program +{ + static void Main() + { +#line (1,1)-(1,100) 5 ""a.razor"" + } +}", changedOptionSet: optionSet); + } } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs b/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs index f1c98c4de478f..d08e55cfcf188 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs index cd64608f7bc44..5b1c167ea3511 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs index ad504f88f359a..2f323d373ff82 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index 10e91ddd6ad76..dba94bbc991dd 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -327,6 +327,7 @@ protected override void ApplyProjectChanges(ProjectChanges projectChanges) projectChanges.ProjectId)); return; } + if (_projectFileLoaderRegistry.TryGetLoaderFromProjectPath(projectPath, out var fileLoader)) { try @@ -375,6 +376,7 @@ protected override void ApplyDocumentTextChanged(DocumentId documentId, SourceTe _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, message, document.Id)); return; } + this.SaveDocumentText(documentId, document.FilePath, text, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); this.OnDocumentTextChanged(documentId, text, PreservationMode.PreserveValue); } @@ -392,6 +394,7 @@ protected override void ApplyAdditionalDocumentTextChanged(DocumentId documentId _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, message, document.Id)); return; } + this.SaveDocumentText(documentId, document.FilePath, text, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); this.OnAdditionalDocumentTextChanged(documentId, text, PreservationMode.PreserveValue); } @@ -435,6 +438,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) { return; } + if (_projectFileLoaderRegistry.TryGetLoaderFromProjectPath(filePath, out _)) { var extension = _applyChangesProjectFile.GetDocumentExtension(info.SourceCodeKind); @@ -530,6 +534,7 @@ protected override void ApplyMetadataReferenceAdded(ProjectId projectId, Metadat _reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, message, projectId)); return; } + _applyChangesProjectFile.AddMetadataReference(metadataReference, identity); this.OnMetadataReferenceAdded(projectId, metadataReference); } @@ -544,6 +549,7 @@ protected override void ApplyMetadataReferenceRemoved(ProjectId projectId, Metad _reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, message, projectId)); return; } + _applyChangesProjectFile.RemoveMetadataReference(metadataReference, identity); this.OnMetadataReferenceRemoved(projectId, metadataReference); } @@ -555,6 +561,7 @@ protected override void ApplyMetadataReferenceRemoved(ProjectId projectId, Metad { return null; } + if (!project.MetadataReferences.Contains(metadataReference)) { project = project.AddMetadataReference(metadataReference); @@ -565,6 +572,7 @@ protected override void ApplyMetadataReferenceRemoved(ProjectId projectId, Metad { return null; } + var symbol = compilation.GetAssemblyOrModuleSymbol(metadataReference) as IAssemblySymbol; return symbol?.Identity; } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Extensions.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Extensions.cs index eec1823393a06..a21ee61b0a180 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Extensions.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Extensions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectMap.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectMap.cs index cae640133d341..242809c13f33d 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectMap.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectMap.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticMode.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticMode.cs index b89e97ed48f7e..f6b0e621d2fe3 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticMode.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticMode.cs @@ -16,5 +16,11 @@ internal enum DiagnosticMode /// responding to LSP pull requests for them. /// Pull, + + /// + /// Default mode - when the option is set to default we use a feature flag to determine if we're + /// is in or + /// + Default, } } diff --git a/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs b/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs index 78b72e5c5264c..69b8e43985984 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs @@ -25,7 +25,7 @@ internal static class InternalDiagnosticsOptions public static readonly Option2 ProcessHiddenDiagnostics = new(nameof(InternalDiagnosticsOptions), nameof(ProcessHiddenDiagnostics), defaultValue: false, storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Process Hidden Diagnostics")); - public static readonly Option2 NormalDiagnosticMode = new(nameof(InternalDiagnosticsOptions), nameof(NormalDiagnosticMode), defaultValue: DiagnosticMode.Push, + public static readonly Option2 NormalDiagnosticMode = new(nameof(InternalDiagnosticsOptions), nameof(NormalDiagnosticMode), defaultValue: DiagnosticMode.Default, storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "NormalDiagnosticMode")); public static readonly Option2 RazorDiagnosticMode = new(nameof(InternalDiagnosticsOptions), nameof(RazorDiagnosticMode), defaultValue: DiagnosticMode.Pull, diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs index 0c35682d73566..3b2a04d15f6eb 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs @@ -577,6 +577,7 @@ public SyntaxNode Declaration(ISymbol symbol) case MethodKind.UserDefinedOperator: return OperatorDeclaration(method); } + break; case SymbolKind.Parameter: @@ -661,6 +662,7 @@ private static bool CanBeDeclared(ISymbol symbol) case MethodKind.Ordinary: return true; } + break; case SymbolKind.NamedType: @@ -674,6 +676,7 @@ private static bool CanBeDeclared(ISymbol symbol) case TypeKind.Delegate: return true; } + break; } diff --git a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs index 881f40c78a723..f120570649767 100644 --- a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs +++ b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs @@ -41,5 +41,7 @@ internal static class WellKnownExperimentNames public const string CloudCache = "Roslyn.CloudCache"; public const string UnnamedSymbolCompletionDisabled = "Roslyn.UnnamedSymbolCompletionDisabled"; public const string RazorLspEditorFeatureFlag = "Razor.LSP.Editor"; + public const string InheritanceMargin = "Roslyn.InheritanceMargin"; + public const string LspPullDiagnosticsFeatureFlag = "Lsp.PullDiagnostics"; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 69f4306df840d..3c47abb899ef1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -16,7 +17,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using ProjectToDocumentMap = Dictionary>>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { @@ -34,6 +35,8 @@ internal partial class FindReferencesSearchEngine private readonly TaskScheduler _scheduler; private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; + private readonly ConcurrentDictionary _symbolToGroup = new(); + public FindReferencesSearchEngine( Solution solution, IImmutableSet? documents, @@ -108,7 +111,7 @@ private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap, Cance private static void ValidateProjectToDocumentMap( ProjectToDocumentMap projectToDocumentMap) { - var set = new HashSet<(SymbolGroup group, ISymbol symbol, IReferenceFinder finder)>(); + var set = new HashSet(); foreach (var documentMap in projectToDocumentMap.Values) { @@ -122,7 +125,33 @@ private static void ValidateProjectToDocumentMap( } } - private ValueTask HandleLocationAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) - => _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken); + private async ValueTask HandleLocationAsync(ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + { + var group = await GetOrCreateSymbolGroupAsync(symbol, cancellationToken).ConfigureAwait(false); + await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + } + + private async ValueTask GetOrCreateSymbolGroupAsync(ISymbol symbol, CancellationToken cancellationToken) + { + // See if this symbol is already associated with a symbol group. + if (!_symbolToGroup.TryGetValue(symbol, out var group)) + { + // If not, compute the group it should be associated with. + group = await DetermineSymbolGroupAsync(symbol, cancellationToken).ConfigureAwait(false); + + // now try to update our mapping. + lock (_symbolToGroup) + { + // Another thread may have beat us, so only do this if we're actually the first to get here. + if (!_symbolToGroup.TryGetValue(symbol, out _)) + { + foreach (var groupSymbol in group.Symbols) + Contract.ThrowIfFalse(_symbolToGroup.TryAdd(groupSymbol, group)); + } + } + } + + return group; + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs index 9ea4179095976..7c570a68dbe33 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs @@ -16,7 +16,7 @@ internal partial class FindReferencesSearchEngine { private async Task ProcessDocumentQueueAsync( Document document, - HashSet<(SymbolGroup group, ISymbol symbol, IReferenceFinder finder)> documentQueue, + HashSet documentQueue, CancellationToken cancellationToken) { await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); @@ -29,8 +29,8 @@ private async Task ProcessDocumentQueueAsync( // start cache for this semantic model FindReferenceCache.Start(model); - foreach (var (group, symbol, finder) in documentQueue) - await ProcessDocumentAsync(document, model, group, symbol, finder, cancellationToken).ConfigureAwait(false); + foreach (var symbol in documentQueue) + await ProcessDocumentAsync(document, model, symbol, cancellationToken).ConfigureAwait(false); } finally { @@ -48,20 +48,19 @@ private async Task ProcessDocumentQueueAsync( private async Task ProcessDocumentAsync( Document document, SemanticModel semanticModel, - SymbolGroup group, ISymbol symbol, - IReferenceFinder finder, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.FindReference_ProcessDocumentAsync, s_logDocument, document, symbol, cancellationToken)) { try { - var references = await finder.FindReferencesInDocumentAsync( - symbol, document, semanticModel, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) + foreach (var finder in _finders) { - await HandleLocationAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + var references = await finder.FindReferencesInDocumentAsync( + symbol, document, semanticModel, _options, cancellationToken).ConfigureAwait(false); + foreach (var (_, location) in references) + await HandleLocationAsync(symbol, location, cancellationToken).ConfigureAwait(false); } } finally diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index 47010180cdfcb..b04db155d06db 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -17,9 +17,9 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = Dictionary>; - using ProjectMap = Dictionary>; - using ProjectToDocumentMap = Dictionary>>; + using DocumentMap = Dictionary>; + using ProjectMap = Dictionary>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { @@ -29,26 +29,26 @@ private async Task CreateProjectToDocumentMapAsync(Project { using (Logger.LogBlock(FunctionId.FindReference_CreateDocumentMapAsync, cancellationToken)) { - using var _ = ArrayBuilder, SymbolGroup, ISymbol, IReferenceFinder)>>.GetInstance(out var tasks); + using var _ = ArrayBuilder, ISymbol)>>.GetInstance(out var tasks); foreach (var (project, projectQueue) in projectMap) { - foreach (var (group, symbol, finder) in projectQueue) + foreach (var symbol in projectQueue) { tasks.Add(Task.Factory.StartNew(() => - DetermineDocumentsToSearchAsync(project, group, symbol, finder, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + DetermineDocumentsToSearchAsync(project, symbol, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } } var results = await Task.WhenAll(tasks).ConfigureAwait(false); var finalMap = new ProjectToDocumentMap(); - foreach (var (documents, group, symbol, finder) in results) + foreach (var (documents, symbol) in results) { foreach (var document in documents) { finalMap.GetOrAdd(document.Project, s_createDocumentMap) - .MultiAdd(document, (group, symbol, finder)); + .MultiAdd(document, symbol); } } @@ -63,14 +63,25 @@ private async Task CreateProjectToDocumentMapAsync(Project } } - private async Task<(ImmutableArray, SymbolGroup, ISymbol, IReferenceFinder)> DetermineDocumentsToSearchAsync( - Project project, SymbolGroup group, ISymbol symbol, IReferenceFinder finder, CancellationToken cancellationToken) + private async Task<(ImmutableArray, ISymbol)> DetermineDocumentsToSearchAsync( + Project project, ISymbol symbol, CancellationToken cancellationToken) { - var documents = await finder.DetermineDocumentsToSearchAsync( - symbol, project, _documents, _options, cancellationToken).ConfigureAwait(false); - var finalDocs = documents.WhereNotNull().Distinct().Where( - d => _documents == null || _documents.Contains(d)).ToImmutableArray(); - return (finalDocs, group, symbol, finder); + using var _ = ArrayBuilder.GetInstance(out var result); + + foreach (var finder in _finders) + { + var documents = await finder.DetermineDocumentsToSearchAsync( + symbol, project, _documents, _options, cancellationToken).ConfigureAwait(false); + + foreach (var document in documents) + { + if (_documents == null || _documents.Contains(document)) + result.Add(document); + } + } + + result.RemoveDuplicates(); + return (result.ToImmutable(), symbol); } private async Task CreateProjectMapAsync(ConcurrentSet symbolGroups, CancellationToken cancellationToken) @@ -84,16 +95,14 @@ private async Task CreateProjectMapAsync(ConcurrentSet { foreach (var symbol in symbolGroup.Symbols) { - foreach (var finder in _finders) + cancellationToken.ThrowIfCancellationRequested(); + var projects = await DependentProjectsFinder.GetDependentProjectsAsync( + _solution, symbol, scope, cancellationToken).ConfigureAwait(false); + + foreach (var project in projects.Distinct().WhereNotNull()) { - cancellationToken.ThrowIfCancellationRequested(); - - var projects = await finder.DetermineProjectsToSearchAsync(symbol, _solution, scope, cancellationToken).ConfigureAwait(false); - foreach (var project in projects.Distinct().WhereNotNull()) - { - if (scope == null || scope.Contains(project)) - projectMap.MultiAdd(project, (symbolGroup, symbol, finder)); - } + if (scope == null || scope.Contains(project)) + projectMap.MultiAdd(project, symbol); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs index c1db21f5f435a..a6b0c28a10bd6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs @@ -6,13 +6,13 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = Dictionary>; + using DocumentMap = Dictionary>; internal partial class FindReferencesSearchEngine { @@ -28,7 +28,7 @@ private async Task ProcessProjectAsync( // make sure we hold onto compilation while we search documents belong to this project var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var documentTasks = new List(); + using var _ = ArrayBuilder.GetInstance(out var documentTasks); foreach (var (document, documentQueue) in documentMap) { if (document.Project == project) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 4b2948900236d..29e40ca4a9817 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -26,10 +26,6 @@ protected AbstractMethodOrPropertyOrEventSymbolReferenceFinder() FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { - // Static methods can't cascade. - if (symbol.IsStatic) - return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; - if (symbol.IsImplementableMember()) { // We have an interface method. Walk down the inheritance hierarchy and find all implementations of diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 113ff4ff76f93..8fbffac24712f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -31,8 +31,6 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken); - public abstract Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken); - public abstract Task> DetermineDocumentsToSearchAsync( ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); @@ -85,18 +83,12 @@ protected static async Task> FindDocumentsAsync(Project protected static Task> FindDocumentsAsync( Project project, IImmutableSet? documents, - bool findInGlobalSuppressions, CancellationToken cancellationToken, params string[] values) { return FindDocumentsAsync(project, documents, async (d, c) => { var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false); - if (findInGlobalSuppressions && info.ContainsGlobalAttributes) - { - return true; - } - foreach (var value in values) { if (!info.ProbablyContainsIdentifier(value)) @@ -109,6 +101,21 @@ protected static Task> FindDocumentsAsync( }, cancellationToken); } + /// + /// Finds all the documents in the provided project that contain a global attribute in them. + /// + protected static Task> FindDocumentsWithGlobalAttributesAsync( + Project project, + IImmutableSet? documents, + CancellationToken cancellationToken) + { + return FindDocumentsAsync(project, documents, async (d, c) => + { + var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false); + return info.ContainsGlobalAttributes; + }, cancellationToken); + } + protected static Task> FindDocumentsAsync( Project project, IImmutableSet? documents, @@ -134,22 +141,19 @@ protected static Task> FindDocumentsAsync( CancellationToken cancellationToken) { if (op == PredefinedOperator.None) - { return SpecializedTasks.EmptyImmutableArray(); - } return FindDocumentsAsync(project, documents, async (d, c) => { var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false); - - // NOTE: Predefined operators can be referenced in global suppression attributes. - return info.ContainsPredefinedOperator(op) || info.ContainsGlobalAttributes; + return info.ContainsPredefinedOperator(op); }, cancellationToken); } protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token) => syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name); + [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] protected static ValueTask> FindReferencesInDocumentUsingIdentifierAsync( ISymbol symbol, string identifier, @@ -162,6 +166,7 @@ protected static ValueTask> FindReferencesInDocum cancellationToken: cancellationToken); } + [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] protected static ValueTask> FindReferencesInDocumentUsingIdentifierAsync( ISymbol symbol, string identifier, @@ -175,49 +180,26 @@ protected static ValueTask> FindReferencesInDocum symbol, identifier, document, semanticModel, symbolsMatch, cancellationToken); } - protected static ValueTask> FindReferencesInDocumentUsingIdentifierAsync( - ISymbol symbol, - string identifier, - Document document, - SemanticModel semanticModel, - Func> symbolsMatchAsync, - CancellationToken cancellationToken) - { - var findInGlobalSuppressions = ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId); - return FindReferencesInDocumentUsingIdentifierAsync( - identifier, document, semanticModel, symbolsMatchAsync, - docCommentId, findInGlobalSuppressions, cancellationToken); - } - [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] protected static async ValueTask> FindReferencesInDocumentUsingIdentifierAsync( + ISymbol _, string identifier, Document document, SemanticModel semanticModel, Func> symbolsMatchAsync, - string? docCommentId, - bool findInGlobalSuppressions, CancellationToken cancellationToken) { var tokens = await GetIdentifierOrGlobalNamespaceTokensWithTextAsync(document, semanticModel, identifier, cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); - var references = await FindReferencesInTokensAsync( + return await FindReferencesInTokensAsync( document, semanticModel, tokens, t => IdentifiersMatch(syntaxFacts, identifier, t), symbolsMatchAsync, cancellationToken).ConfigureAwait(false); - - if (!findInGlobalSuppressions) - return references; - - RoslynDebug.Assert(docCommentId != null); - var referencesInGlobalSuppressions = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - document, semanticModel, syntaxFacts, docCommentId, cancellationToken).ConfigureAwait(false); - return references.AddRange(referencesInGlobalSuppressions); } protected static async Task> GetIdentifierOrGlobalNamespaceTokensWithTextAsync(Document document, SemanticModel semanticModel, string identifier, CancellationToken cancellationToken) @@ -267,23 +249,18 @@ protected static async Task> GetIdentifierOrGlobalNa { return async (node, model) => { - var symbolInfoToMatch = FindReferenceCache.GetSymbolInfo(model, node, cancellationToken); + var symbolInfo = FindReferenceCache.GetSymbolInfo(model, node, cancellationToken); - var symbolToMatch = symbolInfoToMatch.Symbol; - var symbolToMatchCompilation = model.Compilation; - - if (await SymbolFinder.OriginalSymbolsMatchAsync(solution, searchSymbol, symbolInfoToMatch.Symbol, cancellationToken).ConfigureAwait(false)) - { + if (await SymbolFinder.OriginalSymbolsMatchAsync(solution, searchSymbol, symbolInfo.Symbol, cancellationToken).ConfigureAwait(false)) return (matched: true, CandidateReason.None); - } - else if (await symbolInfoToMatch.CandidateSymbols.AnyAsync(static (s, arg) => SymbolFinder.OriginalSymbolsMatchAsync(arg.solution, arg.searchSymbol, s, arg.cancellationToken), (solution, searchSymbol, cancellationToken)).ConfigureAwait(false)) - { - return (matched: true, symbolInfoToMatch.CandidateReason); - } - else + + foreach (var candidate in symbolInfo.CandidateSymbols) { - return (matched: false, CandidateReason.None); + if (await SymbolFinder.OriginalSymbolsMatchAsync(solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + return (matched: true, symbolInfo.CandidateReason); } + + return default; }; } @@ -444,27 +421,23 @@ private static async Task> FindReferencesThroughA CancellationToken cancellationToken) { var syntaxFactsService = document.GetRequiredLanguageService(); - var allAliasReferences = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var aliasSymbol in aliasSymbols) { - var findInGlobalSuppressions = ShouldFindReferencesInGlobalSuppressions(aliasSymbol, out var docCommentId); - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol.Name, document, semanticModel, symbolsMatchAsync, - docCommentId, findInGlobalSuppressions, cancellationToken).ConfigureAwait(false); + aliasSymbol, aliasSymbol.Name, document, semanticModel, symbolsMatchAsync, cancellationToken).ConfigureAwait(false); allAliasReferences.AddRange(aliasReferences); // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, syntaxFactsService, out var simpleName)) { aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - simpleName, document, semanticModel, symbolsMatchAsync, - docCommentId, findInGlobalSuppressions, cancellationToken).ConfigureAwait(false); + aliasSymbol, simpleName, document, semanticModel, symbolsMatchAsync, cancellationToken).ConfigureAwait(false); allAliasReferences.AddRange(aliasReferences); } } - return allAliasReferences.ToImmutableAndFree(); + return allAliasReferences.ToImmutable(); } protected static Task> FindDocumentsWithPredicateAsync(Project project, IImmutableSet? documents, Func predicate, CancellationToken cancellationToken) @@ -491,11 +464,11 @@ protected static Task> FindDocumentsWithImplicitObjectC /// /// If the `node` implicitly matches the `symbol`, then it will be added to `locations`. /// - protected delegate void CollectMatchingReferences(ISymbol symbol, SyntaxNode node, - ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations); + protected delegate void CollectMatchingReferences( + SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations); protected static async Task> FindReferencesInDocumentAsync( - ISymbol symbol, + ISymbol _, Document document, Func isRelevantDocument, CollectMatchingReferences collectMatchingReferences, @@ -508,14 +481,12 @@ protected static async Task> FindReferencesInDocu var semanticFacts = document.GetRequiredLanguageService(); var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var locations); - - var originalUnreducedSymbolDefinition = symbol.GetOriginalUnreducedDefinition(); + using var _1 = ArrayBuilder.GetInstance(out var locations); foreach (var node in syntaxRoot.DescendantNodesAndSelf()) { cancellationToken.ThrowIfCancellationRequested(); - collectMatchingReferences(originalUnreducedSymbolDefinition, node, syntaxFacts, semanticFacts, locations); + collectMatchingReferences(node, syntaxFacts, semanticFacts, locations); } return locations.ToImmutable(); @@ -535,15 +506,15 @@ protected Task> FindReferencesInForEachStatements static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; - void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, SyntaxNode node, - ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) + void CollectMatchingReferences( + SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) { var info = semanticFacts.GetForEachSymbols(semanticModel, node); - if (Matches(info.GetEnumeratorMethod, originalUnreducedSymbolDefinition) || - Matches(info.MoveNextMethod, originalUnreducedSymbolDefinition) || - Matches(info.CurrentProperty, originalUnreducedSymbolDefinition) || - Matches(info.DisposeMethod, originalUnreducedSymbolDefinition)) + if (Matches(info.GetEnumeratorMethod, symbol) || + Matches(info.MoveNextMethod, symbol) || + Matches(info.CurrentProperty, symbol) || + Matches(info.DisposeMethod, symbol)) { var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, semanticModel, syntaxFacts, semanticFacts, cancellationToken); @@ -571,8 +542,8 @@ protected Task> FindReferencesInDeconstructionAsy static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; - void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, SyntaxNode node, - ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) + void CollectMatchingReferences( + SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) { var deconstructMethods = semanticFacts.GetDeconstructionAssignmentMethods(semanticModel, node); if (deconstructMethods.IsEmpty) @@ -581,7 +552,7 @@ void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, Syntax deconstructMethods = semanticFacts.GetDeconstructionForEachMethods(semanticModel, node); } - if (deconstructMethods.Any(m => Matches(m, originalUnreducedSymbolDefinition))) + if (deconstructMethods.Any(m => Matches(m, symbol))) { var location = syntaxFacts.GetDeconstructionReferenceLocation(node); var symbolUsageInfo = GetSymbolUsageInfo(node, semanticModel, syntaxFacts, semanticFacts, cancellationToken); @@ -603,12 +574,12 @@ protected Task> FindReferencesInAwaitExpressionAs static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; - void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, SyntaxNode node, - ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) + void CollectMatchingReferences( + SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) { var awaitExpressionMethod = semanticFacts.GetGetAwaiterMethod(semanticModel, node); - if (Matches(awaitExpressionMethod, originalUnreducedSymbolDefinition)) + if (Matches(awaitExpressionMethod, symbol)) { var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, semanticModel, syntaxFacts, semanticFacts, cancellationToken); @@ -630,8 +601,8 @@ protected Task> FindReferencesInImplicitObjectCre static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; - void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, SyntaxNode node, - ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) + void CollectMatchingReferences( + SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) { if (!syntaxFacts.IsImplicitObjectCreation(node)) { @@ -641,7 +612,7 @@ void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, Syntax var constructor = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; - if (Matches(constructor, originalUnreducedSymbolDefinition)) + if (Matches(constructor, symbol)) { var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, semanticModel, syntaxFacts, semanticFacts, cancellationToken); @@ -652,11 +623,12 @@ void CollectMatchingReferences(ISymbol originalUnreducedSymbolDefinition, Syntax } } - protected static bool Matches(ISymbol? symbol1, ISymbol notNulloriginalUnreducedSymbol2) + protected static bool Matches(ISymbol? symbol1, ISymbol notNullOriginalUnreducedSymbol2) { + Contract.ThrowIfFalse(notNullOriginalUnreducedSymbol2.GetOriginalUnreducedDefinition().Equals(notNullOriginalUnreducedSymbol2)); return symbol1 != null && SymbolEquivalenceComparer.Instance.Equals( symbol1.GetOriginalUnreducedDefinition(), - notNulloriginalUnreducedSymbol2); + notNullOriginalUnreducedSymbol2); } protected static SymbolUsageInfo GetSymbolUsageInfo( @@ -907,13 +879,6 @@ protected abstract ValueTask> FindReferencesInDoc TSymbol symbol, Document document, SemanticModel semanticModel, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public override Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken) - { - return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? DetermineProjectsToSearchAsync(typedSymbol, solution, projects, cancellationToken) - : SpecializedTasks.EmptyImmutableArray(); - } - public override Task> DetermineDocumentsToSearchAsync( ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) @@ -948,13 +913,6 @@ symbol is TSymbol typedSymbol && return SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); } - protected virtual Task> DetermineProjectsToSearchAsync( - TSymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken) - { - return DependentProjectsFinder.GetDependentProjectsAsync( - solution, symbol, projects, cancellationToken); - } - protected virtual Task> DetermineCascadedSymbolsAsync( TSymbol symbol, Solution solution, IImmutableSet? projects, FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, @@ -1026,10 +984,8 @@ protected static ValueTask> FindReferencesInDocum Func? findParentNode, CancellationToken cancellationToken) { - var findInGlobalSuppressions = ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId); var symbolsMatchAsync = GetStandardSymbolsMatchFunction(symbol, findParentNode, document.Project.Solution, cancellationToken); - return FindReferencesInDocumentAsync(document, semanticModel, tokensMatch, - symbolsMatchAsync, docCommentId, findInGlobalSuppressions, cancellationToken); + return FindReferencesInDocumentAsync(document, semanticModel, tokensMatch, symbolsMatchAsync, cancellationToken); } protected static async ValueTask> FindReferencesInDocumentAsync( @@ -1037,24 +993,13 @@ protected static async ValueTask> FindReferencesI SemanticModel semanticModel, Func tokensMatch, Func> symbolsMatchAsync, - string? docCommentId, - bool findInGlobalSuppressions, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Now that we have Doc Comments in place, We are searching for References in the Trivia as well by setting descendIntoTrivia: true var tokens = root.DescendantTokens(descendIntoTrivia: true); - var references = await FindReferencesInTokensAsync(document, semanticModel, tokens, tokensMatch, symbolsMatchAsync, cancellationToken).ConfigureAwait(false); - - if (!findInGlobalSuppressions) - return references; - - RoslynDebug.Assert(docCommentId != null); - var syntaxFacts = document.GetRequiredLanguageService(); - var referencesInGlobalSuppressions = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - document, semanticModel, syntaxFacts, docCommentId, cancellationToken).ConfigureAwait(false); - return references.AddRange(referencesInGlobalSuppressions); + return await FindReferencesInTokensAsync(document, semanticModel, tokens, tokensMatch, symbolsMatchAsync, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs index 5450352c9ed44..2c9d3ecb5c206 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders { internal abstract partial class AbstractReferenceFinder : IReferenceFinder { - protected static bool ShouldFindReferencesInGlobalSuppressions(ISymbol symbol, [NotNullWhen(returnValue: true)] out string? documentationCommentId) + private static bool ShouldFindReferencesInGlobalSuppressions(ISymbol symbol, [NotNullWhen(returnValue: true)] out string? documentationCommentId) { if (!SupportsGlobalSuppression(symbol)) { @@ -54,29 +54,27 @@ static bool SupportsGlobalSuppression(ISymbol symbol) protected static async ValueTask> FindReferencesInDocumentInsideGlobalSuppressionsAsync( Document document, SemanticModel semanticModel, - ISyntaxFacts syntaxFacts, - string docCommentId, + ISymbol symbol, CancellationToken cancellationToken) { + if (!ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId)) + return ImmutableArray.Empty; + // Check if we have any relevant global attributes in this document. var info = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); if (!info.ContainsGlobalAttributes) - { return ImmutableArray.Empty; - } var suppressMessageAttribute = semanticModel.Compilation.SuppressMessageAttributeType(); if (suppressMessageAttribute == null) - { return ImmutableArray.Empty; - } // Check if we have any instances of the symbol documentation comment ID string literals within global attributes. // These string literals represent references to the symbol. if (!TryGetExpectedDocumentationCommentId(docCommentId, out var expectedDocCommentId)) - { return ImmutableArray.Empty; - } + + var syntaxFacts = document.GetRequiredLanguageService(); // We map the positions of documentation ID literals in tree to string literal tokens, // perform semantic checks to ensure these are valid references to the symbol diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 07e8bafc0afb6..992adf5d97b37 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -38,20 +38,20 @@ protected override async Task> DetermineDocumentsToSear CancellationToken cancellationToken) { var typeName = symbol.ContainingType.Name; - var documentsWithName = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, typeName).ConfigureAwait(false); + + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, typeName).ConfigureAwait(false); var documentsWithType = await FindDocumentsAsync(project, documents, symbol.ContainingType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false); + var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(typeName, project.LanguageServices.GetRequiredService(), out var simpleName) - ? await FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, simpleName).ConfigureAwait(false) - : SpecializedCollections.EmptyEnumerable(); + ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) + : ImmutableArray.Empty; var documentsWithImplicitObjectCreations = symbol.MethodKind == MethodKind.Constructor ? await FindDocumentsWithImplicitObjectCreationExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; - return documentsWithName.Concat(documentsWithType, documentsWithImplicitObjectCreations) - .Concat(documentsWithAttribute) - .Distinct() - .ToImmutableArray(); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithName.Concat(documentsWithType, documentsWithImplicitObjectCreations, documentsWithAttribute, documentsWithGlobalAttributes); } private static bool IsPotentialReference( @@ -84,10 +84,13 @@ internal async ValueTask> FindAllReferencesInDocu var normalReferences = await FindReferencesInDocumentWorkerAsync(methodSymbol, document, semanticModel, findParentNode, cancellationToken).ConfigureAwait(false); var nonAliasTypeReferences = await NamedTypeSymbolReferenceFinder.FindNonAliasReferencesAsync(methodSymbol.ContainingType, document, semanticModel, cancellationToken).ConfigureAwait(false); + var aliasReferences = await FindAliasReferencesAsync( nonAliasTypeReferences, methodSymbol, document, semanticModel, findParentNode, cancellationToken).ConfigureAwait(false); - return normalReferences.Concat(aliasReferences); + + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, methodSymbol, cancellationToken).ConfigureAwait(false); + return normalReferences.Concat(aliasReferences, suppressionReferences); } private async Task> FindReferencesInDocumentWorkerAsync( @@ -170,9 +173,7 @@ static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - ISymbol originalUnreducedSymbolDefinition, SyntaxNode node, - ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, - ArrayBuilder locations) + SyntaxNode node, ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, ArrayBuilder locations) { if (!syntaxFacts.IsImplicitObjectCreationExpression(node)) return; @@ -187,7 +188,7 @@ void CollectMatchingReferences( return; var constructor = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; - if (Matches(constructor, originalUnreducedSymbolDefinition)) + if (Matches(constructor, symbol)) { var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, semanticModel, syntaxFacts, semanticFacts, cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 059f739c5c777..f9fb71287efab 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -37,14 +37,16 @@ protected override bool CanFind(IEventSymbol symbol) .Concat(associatedNamedTypes.SelectAsArray(n => ((ISymbol)n, cascadeDirection))); } - protected override Task> DetermineDocumentsToSearchAsync( + protected override async Task> DetermineDocumentsToSearchAsync( IEventSymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name); + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithName.Concat(documentsWithGlobalAttributes); } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index 31ab46d43bc63..ce6d27ca569d1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -12,10 +12,10 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders { - internal partial class ExplicitConversionSymbolReferenceFinder : AbstractReferenceFinder + internal partial class ExplicitConversionSymbolReferenceFinder : AbstractMethodOrPropertyOrEventSymbolReferenceFinder { protected override bool CanFind(IMethodSymbol symbol) - => symbol is { MethodKind: MethodKind.Conversion, Name: WellKnownMemberNames.ExplicitConversionName } && + => symbol is { MethodKind: MethodKind.Conversion, Name: WellKnownMemberNames.ExplicitConversionName or WellKnownMemberNames.ImplicitConversionName } && GetUnderlyingNamedType(symbol.ReturnType) is not null; private static INamedTypeSymbol? GetUnderlyingNamedType(ITypeSymbol symbol) @@ -40,7 +40,7 @@ protected override async Task> DetermineDocumentsToSear var underlyingNamedType = GetUnderlyingNamedType(symbol.ReturnType); Contract.ThrowIfNull(underlyingNamedType); - var documentsWithName = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); var documentsWithType = await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(out var result); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 829999659d2f6..ccc563fb70130 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -27,24 +27,28 @@ protected override bool CanFind(IFieldSymbol symbol) : SpecializedTasks.EmptyImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>(); } - protected override Task> DetermineDocumentsToSearchAsync( + protected override async Task> DetermineDocumentsToSearchAsync( IFieldSymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name); + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithName.Concat(documentsWithGlobalAttributes); } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask> FindReferencesInDocumentAsync( IFieldSymbol symbol, Document document, SemanticModel semanticModel, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, document, semanticModel, cancellationToken); + var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false); + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, symbol, cancellationToken).ConfigureAwait(false); + return nameReferences.Concat(suppressionReferences); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index d6219a3c8f7ab..6284cab2e7785 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -29,22 +29,6 @@ internal interface IReferenceFinder FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken); - /// - /// Called by the find references search engine to determine which projects should be - /// searched for a given symbol. The returned projects will then be searched in parallel. If - /// the implementation does not care about the provided symbol then null can be returned - /// from this method. - /// - /// Implementations should endeavor to keep the list of returned projects as small as - /// possible to keep search time down to a minimum. Returning the entire list of projects in - /// a solution is not recommended (unless, of course, there is reasonable reason to believe - /// there are references in every project). - /// - /// Implementations of this method must be thread-safe. - /// - Task> DetermineProjectsToSearchAsync( - ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken); - /// /// Called by the find references search engine to determine which documents in the supplied /// project need to be searched for references. Only projects returned by diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs index a2b57b754e0ee..5a412595189c7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs @@ -61,7 +61,7 @@ protected override Task> DetermineDocumentsToSearchAsyn // Also, we only look for files that have the name of the owning type. This helps filter // down the set considerably. Contract.ThrowIfNull(symbol.DeclaringMethod); - return FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, symbol.Name, + return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, GetMemberNameWithoutInterfaceName(symbol.DeclaringMethod.Name), symbol.DeclaringMethod.ContainingType.Name); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 7ff58d482f381..80aa784e30f5d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -54,14 +54,15 @@ protected override async Task> DetermineDocumentsToSear { var syntaxFacts = project.LanguageServices.GetRequiredService(); - var documentsWithName = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name).ConfigureAwait(false); + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); var documentsWithType = await FindDocumentsAsync(project, documents, symbol.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false); + var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(symbol.Name, syntaxFacts, out var simpleName) - ? await FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, simpleName).ConfigureAwait(false) + ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) : ImmutableArray.Empty; - return documentsWithName.Concat(documentsWithType) - .Concat(documentsWithAttribute); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithName.Concat(documentsWithType, documentsWithAttribute, documentsWithGlobalAttributes); } private static bool IsPotentialReference( @@ -84,7 +85,8 @@ protected override async ValueTask> FindReference var nonAliasReferences = await FindNonAliasReferencesAsync(namedType, document, semanticModel, cancellationToken).ConfigureAwait(false); var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, null, document.Project.Solution, cancellationToken); var aliasReferences = await FindAliasReferencesAsync(nonAliasReferences, document, semanticModel, symbolsMatch, cancellationToken).ConfigureAwait(false); - return nonAliasReferences.Concat(aliasReferences); + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, namedType, cancellationToken).ConfigureAwait(false); + return nonAliasReferences.Concat(aliasReferences, suppressionReferences); } internal static async ValueTask> FindNonAliasReferencesAsync( @@ -132,8 +134,6 @@ private static ValueTask> FindPredefinedTypeRefer return FindReferencesInDocumentAsync(document, semanticModel, t => IsPotentialReference(predefinedType, syntaxFacts, t), (t, m) => ValueTaskFactory.FromResult((matched: true, reason: CandidateReason.None)), - docCommentId: null, - findInGlobalSuppressions: false, cancellationToken); } @@ -146,8 +146,7 @@ private static ValueTask> FindAttributeReferences var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, null, document.Project.Solution, cancellationToken); var syntaxFacts = document.GetRequiredLanguageService(); return TryGetNameWithoutAttributeSuffix(namedType.Name, syntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(simpleName, document, semanticModel, - symbolsMatch, docCommentId: null, findInGlobalSuppressions: false, cancellationToken) + ? FindReferencesInDocumentUsingIdentifierAsync(namedType, simpleName, document, semanticModel, symbolsMatch, cancellationToken) : new ValueTask>(ImmutableArray.Empty); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index d12c73a8fb677..f4c5df42ff47b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -17,14 +17,16 @@ internal class NamespaceSymbolReferenceFinder : AbstractReferenceFinder true; - protected override Task> DetermineDocumentsToSearchAsync( + protected override async Task> DetermineDocumentsToSearchAsync( INamespaceSymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, GetNamespaceIdentifierName(symbol)); + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, GetNamespaceIdentifierName(symbol)).ConfigureAwait(false); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithGlobalAttributes.Concat(documentsWithName); } private static string GetNamespaceIdentifierName(INamespaceSymbol symbol) @@ -55,12 +57,8 @@ protected override async ValueTask> FindReference var aliasReferences = await FindAliasReferencesAsync(nonAliasReferences, symbol, document, semanticModel, cancellationToken).ConfigureAwait(false); - var suppressionReferences = ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId) - ? await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, - syntaxFacts, docCommentId, cancellationToken).ConfigureAwait(false) - : ImmutableArray.Empty; - - return nonAliasReferences.Concat(aliasReferences).Concat(suppressionReferences); + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, symbol, cancellationToken).ConfigureAwait(false); + return nonAliasReferences.Concat(aliasReferences, suppressionReferences); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 86b07956abb0b..97b038df27d23 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -10,12 +10,12 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders { - internal class OperatorSymbolReferenceFinder : AbstractReferenceFinder + internal class OperatorSymbolReferenceFinder : AbstractMethodOrPropertyOrEventSymbolReferenceFinder { protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.UserDefinedOperator; - protected override Task> DetermineDocumentsToSearchAsync( + protected override async Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, IImmutableSet? documents, @@ -23,10 +23,12 @@ protected override Task> DetermineDocumentsToSearchAsyn CancellationToken cancellationToken) { var op = symbol.GetPredefinedOperator(); - return FindDocumentsAsync(project, documents, op, cancellationToken); + var documentsWithOp = await FindDocumentsAsync(project, documents, op, cancellationToken).ConfigureAwait(false); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithOp.Concat(documentsWithGlobalAttributes); } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask> FindReferencesInDocumentAsync( IMethodSymbol symbol, Document document, SemanticModel semanticModel, @@ -36,9 +38,12 @@ protected override ValueTask> FindReferencesInDoc var syntaxFacts = document.GetRequiredLanguageService(); var op = symbol.GetPredefinedOperator(); - return FindReferencesInDocumentAsync(symbol, document, semanticModel, t => + var opReferences = await FindReferencesInDocumentAsync(symbol, document, semanticModel, t => IsPotentialReference(syntaxFacts, op, t), - cancellationToken); + cancellationToken).ConfigureAwait(false); + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, symbol, cancellationToken).ConfigureAwait(false); + + return opReferences.Concat(suppressionReferences); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index c147242d317eb..277c7e024977d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -84,7 +84,7 @@ protected override async Task> DetermineDocumentsToSear // searches for these, then we should find usages of 'lock(goo)' or 'synclock(goo)' // since they implicitly call those methods. - var ordinaryDocuments = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, methodSymbol.Name).ConfigureAwait(false); + var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, methodSymbol.Name).ConfigureAwait(false); var forEachDocuments = IsForEachMethod(methodSymbol) ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; @@ -97,7 +97,8 @@ protected override async Task> DetermineDocumentsToSear ? await FindDocumentsWithAwaitExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; - return ordinaryDocuments.Concat(forEachDocuments).Concat(deconstructDocuments).Concat(awaitExpressionDocuments); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return ordinaryDocuments.Concat(forEachDocuments, deconstructDocuments, awaitExpressionDocuments, documentsWithGlobalAttributes); } private static bool IsForEachMethod(IMethodSymbol methodSymbol) @@ -120,31 +121,22 @@ protected override async ValueTask> FindReference FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameMatches = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, - document, - semanticModel, - cancellationToken).ConfigureAwait(false); + var nameMatches = await FindReferencesInDocumentUsingSymbolNameAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false); - if (IsForEachMethod(symbol)) - { - var forEachMatches = await FindReferencesInForEachStatementsAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false); - nameMatches = nameMatches.Concat(forEachMatches); - } + var forEachMatches = IsForEachMethod(symbol) + ? await FindReferencesInForEachStatementsAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false) + : ImmutableArray.Empty; - if (IsDeconstructMethod(symbol)) - { - var deconstructMatches = await FindReferencesInDeconstructionAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false); - nameMatches = nameMatches.Concat(deconstructMatches); - } + var deconstructMatches = IsDeconstructMethod(symbol) + ? await FindReferencesInDeconstructionAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false) + : ImmutableArray.Empty; - if (IsGetAwaiterMethod(symbol)) - { - var getAwaiterMatches = await FindReferencesInAwaitExpressionAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false); - nameMatches = nameMatches.Concat(getAwaiterMatches); - } + var getAwaiterMatches = IsGetAwaiterMethod(symbol) + ? await FindReferencesInAwaitExpressionAsync(symbol, document, semanticModel, cancellationToken).ConfigureAwait(false) + : ImmutableArray.Empty; - return nameMatches; + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, symbol, cancellationToken).ConfigureAwait(false); + return nameMatches.Concat(forEachMatches, deconstructMatches, getAwaiterMatches, suppressionReferences); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index 68d79f6ce6aea..2c3b49348348c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -31,7 +31,7 @@ protected override Task> DetermineDocumentsToSearchAsyn // elsewhere as "paramName:" or "paramName:=". We can narrow the search by // filtering down to matches of that form. For now we just return any document // that references something with this name. - return FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, symbol.Name); + return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name); } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index f138a610f5a93..7c6cf9cdc3a13 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -41,24 +41,23 @@ protected override async Task> DetermineDocumentsToSear // First, find any documents with the full name of the accessor (i.e. get_Goo). // This will find explicit calls to the method (which can happen when C# references // a VB parameterized property). - var result = await FindDocumentsAsync( - project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name).ConfigureAwait(false); + var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + var propertyDocuments = ImmutableArray.Empty; if (symbol.AssociatedSymbol is IPropertySymbol property && options.AssociatePropertyReferencesWithSpecificAccessor) { // we want to associate normal property references with the specific accessor being // referenced. So we also need to include documents with our property's name. Just // defer to the Property finder to find these docs and combine them with the result. - var propertyDocuments = await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( + propertyDocuments = await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( property, project, documents, options.With(associatePropertyReferencesWithSpecificAccessor: false), cancellationToken).ConfigureAwait(false); - - result = result.AddRange(propertyDocuments); } - return result; + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return documentsWithName.Concat(propertyDocuments, documentsWithGlobalAttributes); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index f91b23d9ebc7c..63f3f140c535a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -57,7 +57,7 @@ protected override async Task> DetermineDocumentsToSear FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var ordinaryDocuments = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name).ConfigureAwait(false); + var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); var forEachDocuments = IsForEachProperty(symbol) ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) @@ -71,9 +71,8 @@ protected override async Task> DetermineDocumentsToSear ? await FindDocumentWithIndexerMemberCrefAsync(project, documents, cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; - return ordinaryDocuments.Concat(forEachDocuments) - .Concat(elementAccessDocument) - .Concat(indexerMemberCrefDocument); + var documentsWithGlobalAttributes = await FindDocumentsWithGlobalAttributesAsync(project, documents, cancellationToken).ConfigureAwait(false); + return ordinaryDocuments.Concat(forEachDocuments, elementAccessDocument, indexerMemberCrefDocument, documentsWithGlobalAttributes); } private static bool IsForEachProperty(IPropertySymbol symbol) @@ -110,8 +109,8 @@ protected override async ValueTask> FindReference ? await FindIndexerReferencesAsync(symbol, document, semanticModel, options, cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; - return nameReferences.Concat(forEachReferences) - .Concat(indexerReferences); + var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel, symbol, cancellationToken).ConfigureAwait(false); + return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences); } private static Task> FindDocumentWithElementAccessExpressionsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs index 124111318696f..56835c1bd83c1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs @@ -27,7 +27,7 @@ protected override Task> DetermineDocumentsToSearchAsyn // parameter has a different name in different parts that we won't find it. However, // this only happens in error situations. It is not legal in C# to use a different // name for a type parameter in different parts. - return FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, symbol.Name, symbol.ContainingType.Name); + return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, symbol.ContainingType.Name); } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index c61be915c53c7..e9e2640818a1e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -94,7 +94,7 @@ internal static async Task> FindImplementedInterfaceMemb ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) { // Member can only implement interface members if it is an explicit member, or if it is - // public and non static. + // public if (symbol != null) { var explicitImplementations = symbol.ExplicitInterfaceImplementations(); @@ -103,7 +103,7 @@ internal static async Task> FindImplementedInterfaceMemb return explicitImplementations; } else if ( - symbol.DeclaredAccessibility == Accessibility.Public && !symbol.IsStatic && + symbol.DeclaredAccessibility == Accessibility.Public && (symbol.ContainingType.TypeKind == TypeKind.Class || symbol.ContainingType.TypeKind == TypeKind.Struct)) { // Interface implementation is a tricky thing. A method may implement an interface diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 42a85afd2a9b9..cf51a8dd5b372 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -81,7 +81,7 @@ private static async Task ComputeSourceSymbolsChecksumAsync(ProjectSta // Order the documents by FilePath. Default ordering in the RemoteWorkspace is // to be ordered by Guid (which is not consistent across VS sessions). - var textChecksumsTasks = projectState.DocumentStates.States.OrderBy(state => state.FilePath, StringComparer.Ordinal).Select(async state => + var textChecksumsTasks = projectState.DocumentStates.States.Values.OrderBy(state => state.FilePath, StringComparer.Ordinal).Select(async state => { var documentStateChecksum = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); return documentStateChecksum.Text; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs index 82ab256777262..f85592f1418da 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs @@ -45,6 +45,9 @@ private SyntaxTreeIndex( public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken) { + if (!document.SupportsSyntaxTree) + return; + using (Logger.LogBlock(FunctionId.SyntaxTreeIndex_Precalculate, cancellationToken)) { Debug.Assert(document.IsFromPrimaryBranch()); @@ -82,6 +85,9 @@ public static async ValueTask GetRequiredIndexAsync(Document do bool loadOnly, CancellationToken cancellationToken) { + if (!document.SupportsSyntaxTree) + return null; + // See if we already cached an index with this direct document index. If so we can just // return it with no additional work. if (!s_documentToIndex.TryGetValue(document, out var index)) @@ -108,6 +114,9 @@ public static async ValueTask GetRequiredIndexAsync(Document do bool loadOnly, CancellationToken cancellationToken) { + if (!document.SupportsSyntaxTree) + return null; + var checksum = await GetChecksumAsync(document, cancellationToken).ConfigureAwait(false); // Check if we have an index for a previous version of this document. If our diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs index 023c1b101b74a..21b777e411ecd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -11,7 +9,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; @@ -25,15 +22,7 @@ internal interface IDeclaredSymbolInfoFactoryService : ILanguageService { // `rootNamespace` is required for VB projects that has non-global namespace as root namespace, // otherwise we would not be able to get correct data from syntax. - bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNode node, string rootNamespace, out DeclaredSymbolInfo declaredSymbolInfo); - - // Get the name of the receiver type of specified extension method declaration node. - // The returned value would be "" or "[]" for complex types. - string GetReceiverTypeName(SyntaxNode node); - - bool TryGetAliasesFromUsingDirective(SyntaxNode node, out ImmutableArray<(string aliasName, string name)> aliases); - - string GetRootNamespace(CompilationOptions compilationOptions); + void AddDeclaredSymbolInfos(Document document, SyntaxNode root, ArrayBuilder declaredSymbolInfos, Dictionary> extensionMethodInfo, CancellationToken cancellationToken); } internal sealed partial class SyntaxTreeIndex @@ -53,18 +42,23 @@ internal sealed partial class SyntaxTreeIndex /// this string table. The table will have already served its purpose at that point and /// doesn't need to be kept around further. /// - private static readonly ConditionalWeakTable s_projectStringTable = - new(); + private static readonly ConditionalWeakTable s_projectStringTable = new(); private static async Task CreateIndexAsync( Document document, Checksum checksum, CancellationToken cancellationToken) { - var project = document.Project; - var stringTable = GetStringTable(project); + Contract.ThrowIfFalse(document.SupportsSyntaxTree); + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return CreateIndex(document, root, checksum, cancellationToken); + } - var syntaxFacts = document.GetLanguageService(); - var infoFactory = document.GetLanguageService(); - var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive; + private static SyntaxTreeIndex CreateIndex( + Document document, SyntaxNode root, Checksum checksum, CancellationToken cancellationToken) + { + var syntaxFacts = document.GetRequiredLanguageService(); + var infoFactory = document.GetRequiredLanguageService(); + var ignoreCase = !syntaxFacts.IsCaseSensitive; var isCaseSensitive = !ignoreCase; GetIdentifierSet(ignoreCase, out var identifiers, out var escapedIdentifiers); @@ -72,9 +66,8 @@ private static async Task CreateIndexAsync( var stringLiterals = StringLiteralHashSetPool.Allocate(); var longLiterals = LongLiteralHashSetPool.Allocate(); - var declaredSymbolInfos = ArrayBuilder.GetInstance(); - var extensionMethodInfoBuilder = PooledDictionary>.GetInstance(); - using var _ = PooledDictionary.GetInstance(out var usingAliases); + using var _1 = ArrayBuilder.GetInstance(out var declaredSymbolInfos); + using var _2 = PooledDictionary>.GetInstance(out var extensionMethodInfo); try { @@ -98,14 +91,11 @@ private static async Task CreateIndexAsync( if (syntaxFacts != null) { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var rootNamespace = infoFactory.GetRootNamespace(project.CompilationOptions); - foreach (var current in root.DescendantNodesAndTokensAndSelf(descendIntoTrivia: true)) { if (current.IsNode) { - var node = (SyntaxNode)current; + var node = current.AsNode(); containsForEachStatement = containsForEachStatement || syntaxFacts.IsForEachStatement(node); containsLockStatement = containsLockStatement || syntaxFacts.IsLockStatement(node); @@ -123,69 +113,6 @@ private static async Task CreateIndexAsync( containsImplicitObjectCreation = containsImplicitObjectCreation || syntaxFacts.IsImplicitObjectCreationExpression(node); containsGlobalAttributes = containsGlobalAttributes || syntaxFacts.IsGlobalAttribute(node); containsConversion = containsConversion || syntaxFacts.IsConversionExpression(node); - - if (syntaxFacts.IsUsingAliasDirective(node) && infoFactory.TryGetAliasesFromUsingDirective(node, out var aliases)) - { - foreach (var (aliasName, name) in aliases) - { - // In C#, it's valid to declare two alias with identical name, - // as long as they are in different containers. - // - // e.g. - // using X = System.String; - // namespace N - // { - // using X = System.Int32; - // } - // - // If we detect this, we will simply treat extension methods whose - // target type is this alias as complex method. - if (usingAliases.ContainsKey(aliasName)) - { - usingAliases[aliasName] = null; - } - else - { - usingAliases[aliasName] = name; - } - } - } - - // We've received a number of error reports where DeclaredSymbolInfo.GetSymbolAsync() will - // crash because the document's syntax root doesn't contain the span of the node returned - // by TryGetDeclaredSymbolInfo(). There are two possibilities for this crash: - // 1) syntaxFacts.TryGetDeclaredSymbolInfo() is returning a bad span, or - // 2) Document.GetSyntaxRootAsync() (called from DeclaredSymbolInfo.GetSymbolAsync) is - // returning a bad syntax root that doesn't represent the original parsed document. - // By adding the `root.FullSpan.Contains()` check below, if we get similar crash reports in - // the future then we know the problem lies in (2). If, however, the problem is really in - // TryGetDeclaredSymbolInfo, then this will at least prevent us from returning bad spans - // and will prevent the crash from occurring. - if (infoFactory.TryGetDeclaredSymbolInfo(stringTable, node, rootNamespace, out var declaredSymbolInfo)) - { - if (root.FullSpan.Contains(declaredSymbolInfo.Span)) - { - var declaredSymbolInfoIndex = declaredSymbolInfos.Count; - declaredSymbolInfos.Add(declaredSymbolInfo); - - AddExtensionMethodInfo( - infoFactory, - node, - usingAliases, - declaredSymbolInfoIndex, - declaredSymbolInfo, - extensionMethodInfoBuilder); - } - else - { - var message = -$@"Invalid span in {nameof(declaredSymbolInfo)}. -{nameof(declaredSymbolInfo.Span)} = {declaredSymbolInfo.Span} -{nameof(root.FullSpan)} = {root.FullSpan}"; - - FatalError.ReportAndCatch(new InvalidOperationException(message)); - } - } } else { @@ -223,7 +150,7 @@ private static async Task CreateIndexAsync( if (syntaxFacts.IsCharacterLiteral(token)) { - longLiterals.Add((char)token.Value); + longLiterals.Add((char)token.Value!); } if (syntaxFacts.IsNumericLiteral(token)) @@ -247,6 +174,9 @@ private static async Task CreateIndexAsync( } } } + + infoFactory.AddDeclaredSymbolInfos( + document, root, declaredSymbolInfos, extensionMethodInfo, cancellationToken); } return new SyntaxTreeIndex( @@ -273,10 +203,9 @@ private static async Task CreateIndexAsync( containsImplicitObjectCreation, containsGlobalAttributes, containsConversion), - new DeclarationInfo( - declaredSymbolInfos.ToImmutable()), + new DeclarationInfo(declaredSymbolInfos.ToImmutable()), new ExtensionMethodInfo( - extensionMethodInfoBuilder.ToImmutableDictionary( + extensionMethodInfo.ToImmutableDictionary( static kvp => kvp.Key, static kvp => kvp.Value.ToImmutable()))); } @@ -286,58 +215,13 @@ private static async Task CreateIndexAsync( StringLiteralHashSetPool.ClearAndFree(stringLiterals); LongLiteralHashSetPool.ClearAndFree(longLiterals); - foreach (var (_, builder) in extensionMethodInfoBuilder) - { + foreach (var (_, builder) in extensionMethodInfo) builder.Free(); - } - - extensionMethodInfoBuilder.Free(); - declaredSymbolInfos.Free(); } } - private static void AddExtensionMethodInfo( - IDeclaredSymbolInfoFactoryService infoFactory, - SyntaxNode node, - PooledDictionary aliases, - int declaredSymbolInfoIndex, - DeclaredSymbolInfo declaredSymbolInfo, - PooledDictionary> extensionMethodsInfoBuilder) - { - if (declaredSymbolInfo.Kind != DeclaredSymbolInfoKind.ExtensionMethod) - { - return; - } - - var receiverTypeName = infoFactory.GetReceiverTypeName(node); - - // Target type is an alias - if (aliases.TryGetValue(receiverTypeName, out var originalName)) - { - // it is an alias of multiple with identical name, - // simply treat it as a complex method. - if (originalName == null) - { - receiverTypeName = Extensions.ComplexReceiverTypeName; - } - else - { - // replace the alias with its original name. - receiverTypeName = originalName; - } - } - - if (!extensionMethodsInfoBuilder.TryGetValue(receiverTypeName, out var arrayBuilder)) - { - arrayBuilder = ArrayBuilder.GetInstance(); - extensionMethodsInfoBuilder[receiverTypeName] = arrayBuilder; - } - - arrayBuilder.Add(declaredSymbolInfoIndex); - } - - private static StringTable GetStringTable(Project project) - => s_projectStringTable.GetValue(project, _ => StringTable.GetInstance()); + public static StringTable GetStringTable(Project project) + => s_projectStringTable.GetValue(project, static _ => StringTable.GetInstance()); private static void GetIdentifierSet(bool ignoreCase, out HashSet identifiers, out HashSet escapedIdentifiers) { diff --git a/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/AbstractDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/AbstractDeclaredSymbolInfoFactoryService.cs index 32c29a86e044a..c5621b0ccc46f 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/AbstractDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/AbstractDeclaredSymbolInfoFactoryService.cs @@ -8,18 +8,31 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServices { - internal abstract class AbstractDeclaredSymbolInfoFactoryService : IDeclaredSymbolInfoFactoryService + internal abstract class AbstractDeclaredSymbolInfoFactoryService< + TCompilationUnitSyntax, + TUsingDirectiveSyntax, + TNamespaceDeclarationSyntax, + TTypeDeclarationSyntax, + TEnumDeclarationSyntax, + TMemberDeclarationSyntax> : IDeclaredSymbolInfoFactoryService + where TCompilationUnitSyntax : SyntaxNode + where TUsingDirectiveSyntax : SyntaxNode + where TNamespaceDeclarationSyntax : TMemberDeclarationSyntax + where TTypeDeclarationSyntax : TMemberDeclarationSyntax + where TEnumDeclarationSyntax : TMemberDeclarationSyntax + where TMemberDeclarationSyntax : SyntaxNode { - private const string GenericTypeNameManglingString = "`"; - private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" }; - private static readonly ObjectPool>> s_aliasMapListPool = SharedPools.Default>>(); @@ -31,6 +44,33 @@ private static readonly ObjectPool>> s_aliasMapL private static readonly ObjectPool> s_aliasMapPool = SharedPools.StringIgnoreCaseDictionary(); + protected AbstractDeclaredSymbolInfoFactoryService() + { + } + + protected abstract SyntaxList GetChildren(TCompilationUnitSyntax node); + protected abstract SyntaxList GetChildren(TNamespaceDeclarationSyntax node); + protected abstract SyntaxList GetChildren(TTypeDeclarationSyntax node); + protected abstract IEnumerable GetChildren(TEnumDeclarationSyntax node); + + protected abstract SyntaxList GetUsingAliases(TCompilationUnitSyntax node); + protected abstract SyntaxList GetUsingAliases(TNamespaceDeclarationSyntax node); + + protected abstract string GetContainerDisplayName(TMemberDeclarationSyntax namespaceDeclaration); + protected abstract string GetFullyQualifiedContainerName(TMemberDeclarationSyntax memberDeclaration, string rootNamespace); + + protected abstract void AddDeclaredSymbolInfosWorker( + SyntaxNode container, TMemberDeclarationSyntax memberDeclaration, StringTable stringTable, ArrayBuilder declaredSymbolInfos, Dictionary aliases, Dictionary> extensionMethodInfo, string containerDisplayName, string fullyQualifiedContainerName, CancellationToken cancellationToken); + /// + /// Get the name of the target type of specified extension method declaration. + /// The node provided must be an extension method declaration, i.e. calling `TryGetDeclaredSymbolInfo()` + /// on `node` should return a `DeclaredSymbolInfo` of kind `ExtensionMethod`. + /// If the return value is null, then it means this is a "complex" method (as described at ). + /// + protected abstract string GetReceiverTypeName(TMemberDeclarationSyntax node); + protected abstract bool TryGetAliasesFromUsingDirective(TUsingDirectiveSyntax node, out ImmutableArray<(string aliasName, string name)> aliases); + protected abstract string GetRootNamespace(CompilationOptions compilationOptions); + protected static List> AllocateAliasMapList() => s_aliasMapListPool.Allocate(); @@ -60,7 +100,7 @@ protected static string CreateValueTupleTypeString(int elementCount) return ValueTupleName; } // A ValueTuple can have up to 8 type parameters. - return ValueTupleName + GetMetadataAritySuffix(elementCount > 8 ? 8 : elementCount); + return ValueTupleName + ArityUtilities.GetMetadataAritySuffix(elementCount > 8 ? 8 : elementCount); } protected static void FreeAliasMapList(List> list) @@ -110,26 +150,154 @@ protected static void Intern(StringTable stringTable, ArrayBuilder build } } - public static string GetMetadataAritySuffix(int arity) + public void AddDeclaredSymbolInfos( + Document document, + SyntaxNode root, + ArrayBuilder declaredSymbolInfos, + Dictionary> extensionMethodInfo, + CancellationToken cancellationToken) { - Debug.Assert(arity > 0); - return (arity <= s_aritySuffixesOneToNine.Length) - ? s_aritySuffixesOneToNine[arity - 1] - : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture)); + var project = document.Project; + var stringTable = SyntaxTreeIndex.GetStringTable(project); + var rootNamespace = this.GetRootNamespace(project.CompilationOptions); + + using var _1 = PooledDictionary.GetInstance(out var aliases); + + foreach (var usingAlias in GetUsingAliases((TCompilationUnitSyntax)root)) + { + if (this.TryGetAliasesFromUsingDirective(usingAlias, out var current)) + AddAliases(aliases, current); + } + + foreach (var child in GetChildren((TCompilationUnitSyntax)root)) + AddDeclaredSymbolInfos(root, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo, "", "", cancellationToken); } - public abstract bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNode node, string rootNamespace, out DeclaredSymbolInfo declaredSymbolInfo); + private void AddDeclaredSymbolInfos( + SyntaxNode container, + TMemberDeclarationSyntax memberDeclaration, + StringTable stringTable, + string rootNamespace, + ArrayBuilder declaredSymbolInfos, + Dictionary aliases, + Dictionary> extensionMethodInfo, + string containerDisplayName, + string fullyQualifiedContainerName, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - /// - /// Get the name of the target type of specified extension method declaration. - /// The node provided must be an extension method declaration, i.e. calling `TryGetDeclaredSymbolInfo()` - /// on `node` should return a `DeclaredSymbolInfo` of kind `ExtensionMethod`. - /// If the return value is null, then it means this is a "complex" method (as described at ). - /// - public abstract string GetReceiverTypeName(SyntaxNode node); + if (memberDeclaration is TNamespaceDeclarationSyntax namespaceDeclaration) + { + var innerContainerDisplayName = GetContainerDisplayName(memberDeclaration); + var innerFullyQualifiedContainerName = GetFullyQualifiedContainerName(memberDeclaration, rootNamespace); - public abstract bool TryGetAliasesFromUsingDirective(SyntaxNode node, out ImmutableArray<(string aliasName, string name)> aliases); + foreach (var usingAlias in GetUsingAliases(namespaceDeclaration)) + { + if (this.TryGetAliasesFromUsingDirective(usingAlias, out var current)) + AddAliases(aliases, current); + } - public abstract string GetRootNamespace(CompilationOptions compilationOptions); + foreach (var child in GetChildren(namespaceDeclaration)) + { + AddDeclaredSymbolInfos( + memberDeclaration, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo, + innerContainerDisplayName, innerFullyQualifiedContainerName, cancellationToken); + } + } + else if (memberDeclaration is TTypeDeclarationSyntax baseTypeDeclaration) + { + var innerContainerDisplayName = GetContainerDisplayName(memberDeclaration); + var innerFullyQualifiedContainerName = GetFullyQualifiedContainerName(memberDeclaration, rootNamespace); + foreach (var child in GetChildren(baseTypeDeclaration)) + { + AddDeclaredSymbolInfos( + memberDeclaration, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo, + innerContainerDisplayName, innerFullyQualifiedContainerName, cancellationToken); + } + } + else if (memberDeclaration is TEnumDeclarationSyntax enumDeclaration) + { + var innerContainerDisplayName = GetContainerDisplayName(memberDeclaration); + var innerFullyQualifiedContainerName = GetFullyQualifiedContainerName(memberDeclaration, rootNamespace); + foreach (var child in GetChildren(enumDeclaration)) + { + AddDeclaredSymbolInfos( + memberDeclaration, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo, + innerContainerDisplayName, innerFullyQualifiedContainerName, cancellationToken); + } + } + + AddDeclaredSymbolInfosWorker( + container, + memberDeclaration, + stringTable, + declaredSymbolInfos, + aliases, + extensionMethodInfo, + containerDisplayName, + fullyQualifiedContainerName, + cancellationToken); + } + + protected void AddExtensionMethodInfo( + TMemberDeclarationSyntax node, + Dictionary aliases, + int declaredSymbolInfoIndex, + Dictionary> extensionMethodsInfoBuilder) + { + var receiverTypeName = this.GetReceiverTypeName(node); + + // Target type is an alias + if (aliases.TryGetValue(receiverTypeName, out var originalName)) + { + // it is an alias of multiple with identical name, + // simply treat it as a complex method. + if (originalName == null) + { + receiverTypeName = FindSymbols.Extensions.ComplexReceiverTypeName; + } + else + { + // replace the alias with its original name. + receiverTypeName = originalName; + } + } + + if (!extensionMethodsInfoBuilder.TryGetValue(receiverTypeName, out var arrayBuilder)) + { + arrayBuilder = ArrayBuilder.GetInstance(); + extensionMethodsInfoBuilder[receiverTypeName] = arrayBuilder; + } + + arrayBuilder.Add(declaredSymbolInfoIndex); + } + + private static void AddAliases(Dictionary allAliases, ImmutableArray<(string aliasName, string name)> aliases) + { + foreach (var (aliasName, name) in aliases) + { + // In C#, it's valid to declare two alias with identical name, + // as long as they are in different containers. + // + // e.g. + // using X = System.String; + // namespace N + // { + // using X = System.Int32; + // } + // + // If we detect this, we will simply treat extension methods whose + // target type is this alias as complex method. + if (allAliases.ContainsKey(aliasName)) + { + allAliases[aliasName] = null; + } + else + { + allAliases[aliasName] = name; + } + } + } } } diff --git a/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/ArityUtilities.cs b/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/ArityUtilities.cs new file mode 100644 index 0000000000000..6270ef9aed9b4 --- /dev/null +++ b/src/Workspaces/Core/Portable/LanguageServices/DeclaredSymbolFactoryService/ArityUtilities.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System.Diagnostics; +using System.Globalization; + +namespace Microsoft.CodeAnalysis.LanguageServices +{ + internal static class ArityUtilities + { + private const string GenericTypeNameManglingString = "`"; + private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" }; + + public static string GetMetadataAritySuffix(int arity) + { + Debug.Assert(arity > 0); + return (arity <= s_aritySuffixesOneToNine.Length) + ? s_aritySuffixesOneToNine[arity - 1] + : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture)); + } + } +} diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index ce4e50284c83b..606559a0d60f3 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -8,7 +8,7 @@ netcoreapp3.1;netstandard2.0 $(DefineConstants);WORKSPACE true - partial + partial true @@ -48,6 +48,7 @@ + @@ -59,6 +60,7 @@ + diff --git a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs index c984ef25b7ff3..25a09dad3771e 100644 --- a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs +++ b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs @@ -44,11 +44,11 @@ public AbstractRecommendationServiceRunner( // This code is to help give intellisense in the following case: // query.Include(a => a.SomeProperty).ThenInclude(a => a. // where there are more than one overloads of ThenInclude accepting different types of parameters. - private ImmutableArray GetMemberSymbolsForParameter(IParameterSymbol parameter, int position, bool useBaseReferenceAccessibility) + private ImmutableArray GetMemberSymbolsForParameter(IParameterSymbol parameter, int position, bool useBaseReferenceAccessibility, bool unwrapNullable) { var symbols = TryGetMemberSymbolsForLambdaParameter(parameter, position); return symbols.IsDefault - ? GetMemberSymbols(parameter.Type, position, excludeInstance: false, useBaseReferenceAccessibility) + ? GetMemberSymbols(parameter.Type, position, excludeInstance: false, useBaseReferenceAccessibility, unwrapNullable) : symbols; } @@ -110,7 +110,7 @@ private ImmutableArray TryGetMemberSymbolsForLambdaParameter(IParameter // parameter the compiler inferred as it may have made a completely suitable inference for it. return parameterTypeSymbols .Concat(parameter.Type) - .SelectMany(parameterTypeSymbol => GetMemberSymbols(parameterTypeSymbol, position, excludeInstance: false, useBaseReferenceAccessibility: false)) + .SelectMany(parameterTypeSymbol => GetMemberSymbols(parameterTypeSymbol, position, excludeInstance: false, useBaseReferenceAccessibility: false, unwrapNullable: false)) .ToImmutableArray(); } @@ -299,16 +299,22 @@ protected ImmutableArray GetMemberSymbols( ISymbol container, int position, bool excludeInstance, - bool useBaseReferenceAccessibility) + bool useBaseReferenceAccessibility, + bool unwrapNullable) { // For a normal parameter, we have a specialized codepath we use to ensure we properly get lambda parameter // information that the compiler may fail to give. if (container is IParameterSymbol parameter) - return GetMemberSymbolsForParameter(parameter, position, useBaseReferenceAccessibility); + return GetMemberSymbolsForParameter(parameter, position, useBaseReferenceAccessibility, unwrapNullable); if (container is not INamespaceOrTypeSymbol namespaceOrType) return ImmutableArray.Empty; + if (unwrapNullable && namespaceOrType is ITypeSymbol typeSymbol) + { + namespaceOrType = typeSymbol.RemoveNullableIfPresent(); + } + return useBaseReferenceAccessibility ? _context.SemanticModel.LookupBaseMembers(position) : LookupSymbolsInContainer(namespaceOrType, position, excludeInstance); diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index a3ba3ff2515df..e90a954880bbd 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -724,9 +724,10 @@ private async Task AddDocumentsWithPotentialConflictsAsync(IEnumerable foreach (var document in documents) { if (_documentsIdsToBeCheckedForConflict.Contains(document.Id)) - { continue; - } + + if (!document.SupportsSyntaxTree) + continue; var info = await SyntaxTreeIndex.GetRequiredIndexAsync(document, _cancellationToken).ConfigureAwait(false); if (info.ProbablyContainsEscapedIdentifier(_originalText)) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs index 85ea4c5dc0d83..8928d65495161 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs @@ -15,6 +15,7 @@ using System.Xml.Linq; using System.Xml.XPath; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -269,7 +270,7 @@ private static DocumentationComment GetDocumentationComment(ISymbol symbol, Hash element.ReplaceNodes(RewriteMany(symbol, visitedSymbols, compilation, element.Nodes().ToArray(), cancellationToken)); xmlText = element.ToString(SaveOptions.DisableFormatting); } - catch + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { } } @@ -424,28 +425,46 @@ private static XNode[] RewriteMany(ISymbol symbol, HashSet? visitedSymb } } - var loadedElements = TrySelectNodes(document, xpathValue); - if (loadedElements is null) - { - return Array.Empty(); - } + // Consider the following code, we want Test.Clone to say "Clones a Test" instead of "Clones a int", thus + // we rewrite `typeparamref`s as cref pointing to the correct type: + /* + public class Test : ICloneable> + { + /// + public Test Clone() => new(); + } - if (loadedElements?.Length > 0) + /// A type that has clonable instances. + /// The type of instances that can be cloned. + public interface ICloneable + { + /// Clones a . + public T Clone(); + } + */ + // Note: there is no way to cref an instantiated generic type. See https://github.com/dotnet/csharplang/issues/401 + var typeParameterRefs = document.Descendants(DocumentationCommentXmlNames.TypeParameterReferenceElementName).ToImmutableArray(); + foreach (var typeParameterRef in typeParameterRefs) { - // change the current XML file path for nodes contained in the document: - // prototype(inheritdoc): what should the file path be? - var result = RewriteMany(symbol, visitedSymbols, compilation, loadedElements, cancellationToken); - - // The elements could be rewritten away if they are includes that refer to invalid - // (but existing and accessible) XML files. If this occurs, behave as if we - // had failed to find any XPath results (as in Dev11). - if (result.Length > 0) + if (typeParameterRef.Attribute(DocumentationCommentXmlNames.NameAttributeName) is var typeParamName) { - return result; + var index = symbol.OriginalDefinition.GetAllTypeParameters().IndexOf(p => p.Name == typeParamName.Value); + if (index >= 0) + { + var typeArgs = symbol.GetAllTypeArguments(); + if (index < typeArgs.Length) + { + var docId = typeArgs[index].GetDocumentationCommentId(); + var replacement = new XElement(DocumentationCommentXmlNames.SeeElementName); + replacement.SetAttributeValue(DocumentationCommentXmlNames.CrefAttributeName, docId); + typeParameterRef.ReplaceWith(replacement); + } + } } } - return null; + var loadedElements = TrySelectNodes(document, xpathValue); + return loadedElements ?? Array.Empty(); } catch (XmlException) { @@ -487,7 +506,8 @@ private static XNode[] RewriteMany(ISymbol symbol, HashSet? visitedSymb { if (typeSymbol.TypeKind == TypeKind.Class) { - // prototype(inheritdoc): when does base class take precedence over interface? + // Classes use the base type as the default inheritance candidate. A different target (e.g. an + // interface) can be provided via the 'path' attribute. return typeSymbol.BaseType; } else if (typeSymbol.TypeKind == TypeKind.Interface) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs index 49c76de2e08fb..cd195f4789a1e 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs @@ -170,7 +170,6 @@ where SymbolEquivalenceComparer.Instance.Equals(explicitInterfaceMethod, constru from baseType in typeSymbol.GetBaseTypesAndThis() from member in baseType.GetMembers(constructedInterfaceMember.Name).OfType() where member.DeclaredAccessibility == Accessibility.Public && - !member.IsStatic && SignatureComparer.Instance.HaveSameSignatureAndConstraintsAndReturnTypeAndAccessors(member, constructedInterfaceMember, syntaxFacts.IsCaseSensitive) select member; diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider+NullOperationListener.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider+NullOperationListener.cs index 416aac4067946..823fdd4131ffa 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider+NullOperationListener.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider+NullOperationListener.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.TestHooks { @@ -19,10 +20,44 @@ public IAsyncToken BeginAsyncOperation( [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) => EmptyAsyncToken.Instance; - public async Task Delay(TimeSpan delay, CancellationToken cancellationToken) + public Task Delay(TimeSpan delay, CancellationToken cancellationToken) { - await Task.Delay(delay, cancellationToken).ConfigureAwait(false); - return true; + // This could be as simple as: + // await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + // return true; + // However, whereas in general cancellation is expected to be rare and thus throwing + // an exception in response isn't very impactful, here it's expected to be the case + // more often than not as the operation is being used to delay an operation because + // it's expected something else is going to happen to obviate the need for that + // operation. Thus, we can spend a little more code avoiding the additional throw + // for the common case of an exception occurring. + + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + var t = Task.Delay(delay, cancellationToken); + if (t.IsCompleted) + { + // Avoid ContinueWith overheads for a 0 delay or if race conditions resulted + // in the delay task being complete by the time we checked. + return t.Status == TaskStatus.RanToCompletion + ? SpecializedTasks.True + : Task.FromCanceled(cancellationToken); + } + + return t.ContinueWith( + _ => true, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled, + TaskScheduler.Default); + + // Note the above passes CancellationToken.None and TaskContinuationOptions.NotOnCanceled. + // That's cheaper than passing cancellationToken and with the same semantics except + // that if the returned task does end up being canceled, any operation canceled exception + // thrown won't contain the cancellationToken. If that ends up being impactful, it can + // be switched to use `cancellationToken, TaskContinuationOptions.ExecuteSynchronously`. } } } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 759e1e45b6a54..c1d410a65b6b0 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -13,6 +13,7 @@ internal static class FeatureAttribute public const string Classification = nameof(Classification); public const string CodeModel = nameof(CodeModel); public const string CompletionSet = nameof(CompletionSet); + public const string DesignerAttributes = nameof(DesignerAttributes); public const string DiagnosticService = nameof(DiagnosticService); public const string EncapsulateField = nameof(EncapsulateField); public const string ErrorList = nameof(ErrorList); @@ -33,6 +34,7 @@ internal static class FeatureAttribute public const string NavigationBar = nameof(NavigationBar); public const string Outlining = nameof(Outlining); public const string PackageInstaller = nameof(PackageInstaller); + public const string PersistentStorage = nameof(PersistentStorage); public const string ReferenceHighlighting = nameof(ReferenceHighlighting); public const string Rename = nameof(Rename); public const string RenameTracking = nameof(RenameTracking); @@ -42,6 +44,7 @@ internal static class FeatureAttribute public const string SignatureHelp = nameof(SignatureHelp); public const string Snippets = nameof(Snippets); public const string SolutionCrawler = nameof(SolutionCrawler); + public const string Telemetry = nameof(Telemetry); public const string TodoCommentList = nameof(TodoCommentList); public const string LanguageServer = nameof(LanguageServer); public const string Workspace = nameof(Workspace); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingDelay.cs b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingDelay.cs deleted file mode 100644 index ccdd3f39c2a26..0000000000000 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingDelay.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Roslyn.Utilities; - -namespace Roslyn.Utilities -{ - internal sealed class AsyncBatchingDelay - { - private readonly AsyncBatchingWorkQueue _workQueue; - private readonly Func _processAsync; - - public AsyncBatchingDelay( - TimeSpan delay, - Func processAsync, - IAsynchronousOperationListener? asyncListener, - CancellationToken cancellationToken) - { - _processAsync = processAsync; - - // We use an AsyncBatchingWorkQueue with a boolean, and just always add the - // same value at all times. - _workQueue = new AsyncBatchingWorkQueue( - delay, - OnNotifyAsync, - equalityComparer: EqualityComparer.Default, - asyncListener, - cancellationToken); - } - - private Task OnNotifyAsync(ImmutableArray _, CancellationToken cancellationToken) - { - return _processAsync(cancellationToken); - } - - public void RequeueWork() - { - // Value doesn't matter here as long as we're consistent. - _workQueue.AddWork(item: false); - } - } -} diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`0.cs b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`0.cs new file mode 100644 index 0000000000000..a5ecc1df789f5 --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`0.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.TestHooks; + +namespace Roslyn.Utilities +{ + /// + internal class AsyncBatchingWorkQueue : AsyncBatchingWorkQueue + { + public AsyncBatchingWorkQueue( + TimeSpan delay, + Func processBatchAsync, + IAsynchronousOperationListener asyncListener, + CancellationToken cancellationToken) + : base(delay, Convert(processBatchAsync), EqualityComparer.Default, asyncListener, cancellationToken) + { + } + + private static Func, CancellationToken, ValueTask> Convert(Func processBatchAsync) + => (items, ct) => processBatchAsync(ct); + + public void AddWork() + => base.AddWork(default(VoidResult)); + } +} diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`1.cs b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`1.cs new file mode 100644 index 0000000000000..5e17e3090a256 --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`1.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.TestHooks; + +namespace Roslyn.Utilities +{ + /// + internal class AsyncBatchingWorkQueue : AsyncBatchingWorkQueue + { + public AsyncBatchingWorkQueue( + TimeSpan delay, + Func, CancellationToken, ValueTask> processBatchAsync, + IAsynchronousOperationListener asyncListener, + CancellationToken cancellationToken) + : this(delay, + processBatchAsync, + equalityComparer: null, + asyncListener, + cancellationToken) + { + } + + public AsyncBatchingWorkQueue( + TimeSpan delay, + Func, CancellationToken, ValueTask> processBatchAsync, + IEqualityComparer? equalityComparer, + IAsynchronousOperationListener asyncListener, + CancellationToken cancellationToken) + : base(delay, Convert(processBatchAsync), equalityComparer, asyncListener, cancellationToken) + { + } + + private static Func, CancellationToken, ValueTask> Convert(Func, CancellationToken, ValueTask> processBatchAsync) + => async (items, ct) => + { + await processBatchAsync(items, ct).ConfigureAwait(false); + return default; + }; + + public new Task WaitUntilCurrentBatchCompletesAsync() + => base.WaitUntilCurrentBatchCompletesAsync(); + } +} diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue.cs b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs similarity index 67% rename from src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue.cs rename to src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs index 0d92a1461e5fa..debca352c369c 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs @@ -19,7 +19,7 @@ namespace Roslyn.Utilities /// along to be worked on. Rounds of processing happen serially, only starting up after a /// previous round has completed. /// - internal class AsyncBatchingWorkQueue + internal class AsyncBatchingWorkQueue { /// /// Delay we wait after finishing the processing of one batch and starting up on then. @@ -34,8 +34,8 @@ internal class AsyncBatchingWorkQueue /// /// Callback to actually perform the processing of the next batch of work. /// - private readonly Func, CancellationToken, Task> _processBatchAsync; - private readonly IAsynchronousOperationListener? _asyncListener; + private readonly Func, CancellationToken, ValueTask> _processBatchAsync; + private readonly IAsynchronousOperationListener _asyncListener; private readonly CancellationToken _cancellationToken; #region protected by lock @@ -63,7 +63,7 @@ internal class AsyncBatchingWorkQueue /// Task kicked off to do the next batch of processing of . These /// tasks form a chain so that the next task only processes when the previous one completes. /// - private Task _updateTask = Task.CompletedTask; + private Task _updateTask = Task.FromResult(default(TResult)); /// /// Whether or not there is an existing task in flight that will process the current batch @@ -76,23 +76,9 @@ internal class AsyncBatchingWorkQueue public AsyncBatchingWorkQueue( TimeSpan delay, - Func, CancellationToken, Task> processBatchAsync, - CancellationToken cancellationToken) - : this(delay, - processBatchAsync, - equalityComparer: null, - asyncListener: null, - cancellationToken) - { - } - - /// Callback to add the new items to the current batch. It is legal to mutate - /// the current batch (for example, clearing the batch or deduplicating) - public AsyncBatchingWorkQueue( - TimeSpan delay, - Func, CancellationToken, Task> processBatchAsync, + Func, CancellationToken, ValueTask> processBatchAsync, IEqualityComparer? equalityComparer, - IAsynchronousOperationListener? asyncListener, + IAsynchronousOperationListener asyncListener, CancellationToken cancellationToken) { _delay = delay; @@ -122,11 +108,35 @@ public void AddWork(IEnumerable items) { // add our work to the set we'll process in the next batch. AddItemsToBatch(items); - TryKickOffNextBatchTask(); + + if (!_taskInFlight) + { + // No in-flight task. Kick one off to process these messages a second from now. + // We always attach the task to the previous one so that notifications to the ui + // follow the same order as the notification the OOP server sent to us. + _updateTask = ContinueAfterDelay(_updateTask); + _taskInFlight = true; + } + } + + return; + + async Task ContinueAfterDelay(Task lastTask) + { + using var _ = _asyncListener.BeginAsyncOperation(nameof(AddWork)); + await lastTask.ConfigureAwait(false); + + // Ensure that we always yield the current thread this is necessary for correctness as we are called + // inside a lock that _taskInFlight to true. We must ensure that the work to process the next batch + // must be on another thread that runs afterwards, can only grab the thread once we release it and will + // then reset that bool back to false + await Task.Yield().ConfigureAwait(false); + await _asyncListener.Delay(_delay, _cancellationToken).ConfigureAwait(false); + return await ProcessNextBatchAsync(_cancellationToken).ConfigureAwait(false); } } - public Task WaitUntilCurrentBatchCompletesAsync() + public Task WaitUntilCurrentBatchCompletesAsync() { lock (_gate) return _updateTask; @@ -149,42 +159,7 @@ private void AddItemsToBatch(IEnumerable items) } } - private void TryKickOffNextBatchTask() - { - Debug.Assert(Monitor.IsEntered(_gate)); - - if (!_taskInFlight) - { - // No in-flight task. Kick one off to process these messages a second from now. - // We always attach the task to the previous one so that notifications to the ui - // follow the same order as the notification the OOP server sent to us. - if (_asyncListener is object) - { - var token = _asyncListener.BeginAsyncOperation(nameof(TryKickOffNextBatchTask)); - - _updateTask = _updateTask.ContinueWithAfterDelayFromAsync( - _ => ProcessNextBatchAsync(_cancellationToken), - _cancellationToken, - (int)_delay.TotalMilliseconds, - _asyncListener, - TaskContinuationOptions.RunContinuationsAsynchronously, - TaskScheduler.Default).CompletesAsyncOperation(token); - } - else - { - _updateTask = _updateTask.ContinueWithAfterDelayFromAsync( - _ => ProcessNextBatchAsync(_cancellationToken), - _cancellationToken, - (int)_delay.TotalMilliseconds, - TaskContinuationOptions.RunContinuationsAsynchronously, - TaskScheduler.Default); - } - - _taskInFlight = true; - } - } - - private Task ProcessNextBatchAsync(CancellationToken cancellationToken) + private ValueTask ProcessNextBatchAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return _processBatchAsync(GetNextBatchAndResetQueue(), _cancellationToken); diff --git a/src/Workspaces/Core/Portable/SolutionCrawler/IncrementalAnalyzerProviderMetadata.cs b/src/Workspaces/Core/Portable/SolutionCrawler/IncrementalAnalyzerProviderMetadata.cs index f6a8f05de4c65..af4b6cc9ba553 100644 --- a/src/Workspaces/Core/Portable/SolutionCrawler/IncrementalAnalyzerProviderMetadata.cs +++ b/src/Workspaces/Core/Portable/SolutionCrawler/IncrementalAnalyzerProviderMetadata.cs @@ -15,15 +15,15 @@ internal class IncrementalAnalyzerProviderMetadata : WorkspaceKindMetadata public bool HighPriorityForActiveFile { get; } public string Name { get; } - public IncrementalAnalyzerProviderMetadata(IDictionary data) : - base(data) + public IncrementalAnalyzerProviderMetadata(IDictionary data) + : base(data) { this.HighPriorityForActiveFile = (bool)data.GetValueOrDefault("HighPriorityForActiveFile"); this.Name = (string)data.GetValueOrDefault("Name"); } - public IncrementalAnalyzerProviderMetadata(string name, bool highPriorityForActiveFile, params string[] workspaceKinds) : - base(workspaceKinds) + public IncrementalAnalyzerProviderMetadata(string name, bool highPriorityForActiveFile, params string[] workspaceKinds) + : base(workspaceKinds) { this.HighPriorityForActiveFile = highPriorityForActiveFile; this.Name = name; diff --git a/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs index 4408ca3467b3c..b912912731405 100644 --- a/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; // When building for source-build, there is no sqlite dependency #if !DOTNET_BUILD_FROM_SOURCE @@ -36,12 +37,16 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) #else private readonly SQLiteConnectionPoolService _connectionPoolService; + private readonly IAsynchronousOperationListener _asyncListener; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DesktopPersistenceStorageServiceFactory(SQLiteConnectionPoolService connectionPoolService) + public DesktopPersistenceStorageServiceFactory( + SQLiteConnectionPoolService connectionPoolService, + IAsynchronousOperationListenerProvider asyncOperationListenerProvider) { _connectionPoolService = connectionPoolService; + _asyncListener = asyncOperationListenerProvider.GetListener(FeatureAttribute.PersistentStorage); } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) @@ -55,7 +60,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) switch (database) { case StorageDatabase.SQLite: - return new SQLitePersistentStorageService(options, _connectionPoolService, locationService); + return new SQLitePersistentStorageService(options, _connectionPoolService, locationService, _asyncListener); case StorageDatabase.CloudCache: var factory = workspaceServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs index 418b29c4b8240..4b4970d5dc1e1 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SQLite.v2.Interop; using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; @@ -45,6 +46,7 @@ private SQLitePersistentStorage( string workingFolderPath, string solutionFilePath, string databaseFile, + IAsynchronousOperationListener asyncListener, IPersistentStorageFaultInjector? faultInjector) : base(workingFolderPath, solutionFilePath, databaseFile) { @@ -63,10 +65,10 @@ private SQLitePersistentStorage( CancellationToken.None)!; // Create a delay to batch up requests to flush. We'll won't flush more than every FlushAllDelayMS. - _flushQueue = new AsyncBatchingDelay( + _flushQueue = new AsyncBatchingWorkQueue( TimeSpan.FromMilliseconds(FlushAllDelayMS), FlushInMemoryDataToDiskIfNotShutdownAsync, - asyncListener: null, + asyncListener, _shutdownTokenSource.Token); } @@ -75,9 +77,11 @@ private SQLitePersistentStorage( string workingFolderPath, string solutionFilePath, string databaseFile, + IAsynchronousOperationListener asyncListener, IPersistentStorageFaultInjector? faultInjector) { - var sqlStorage = new SQLitePersistentStorage(connectionPoolService, workingFolderPath, solutionFilePath, databaseFile, faultInjector); + var sqlStorage = new SQLitePersistentStorage( + connectionPoolService, workingFolderPath, solutionFilePath, databaseFile, asyncListener, faultInjector); if (sqlStorage._connectionPool is null) { // The connection pool failed to initialize diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs index 9baaf0089c3ba..5b19565285155 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.SQLite.v2 @@ -19,25 +20,29 @@ internal class SQLitePersistentStorageService : AbstractSQLitePersistentStorageS private const string PersistentStorageFileName = "storage.ide"; private readonly SQLiteConnectionPoolService _connectionPoolService; + private readonly IAsynchronousOperationListener _asyncListener; private readonly OptionSet _options; private readonly IPersistentStorageFaultInjector? _faultInjector; public SQLitePersistentStorageService( OptionSet options, SQLiteConnectionPoolService connectionPoolService, - IPersistentStorageLocationService locationService) + IPersistentStorageLocationService locationService, + IAsynchronousOperationListener asyncListener) : base(locationService) { _options = options; _connectionPoolService = connectionPoolService; + _asyncListener = asyncListener; } public SQLitePersistentStorageService( OptionSet options, SQLiteConnectionPoolService connectionPoolService, IPersistentStorageLocationService locationService, + IAsynchronousOperationListener asyncListener, IPersistentStorageFaultInjector? faultInjector) - : this(options, connectionPoolService, locationService) + : this(options, connectionPoolService, locationService, asyncListener) { _faultInjector = faultInjector; } @@ -65,6 +70,7 @@ protected override string GetDatabaseFilePath(string workingFolderPath) workingFolderPath, solutionKey.FilePath, databaseFilePath, + _asyncListener, _faultInjector)); } diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs index 7bb665bd52a99..4389e046bda35 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs @@ -16,19 +16,19 @@ internal partial class SQLitePersistentStorage /// A queue to batch up flush requests and ensure that we don't issue then more often than every . /// - private readonly AsyncBatchingDelay _flushQueue; + private readonly AsyncBatchingWorkQueue _flushQueue; private void EnqueueFlushTask() { - _flushQueue.RequeueWork(); + _flushQueue.AddWork(); } - private Task FlushInMemoryDataToDiskIfNotShutdownAsync(CancellationToken cancellationToken) + private async ValueTask FlushInMemoryDataToDiskIfNotShutdownAsync(CancellationToken cancellationToken) { // When we are asked to flush, go actually acquire the write-scheduler and perform the actual writes from // it. Note: this is only called max every FlushAllDelayMS. So we don't bother trying to avoid the delegate // allocation here. - return PerformWriteAsync(FlushInMemoryDataToDisk, cancellationToken); + await PerformWriteAsync(FlushInMemoryDataToDisk, cancellationToken).ConfigureAwait(false); } private Task FlushWritesOnCloseAsync() diff --git a/src/Workspaces/Core/Portable/Workspace/Host/CompilationFactory/ICompilationFactoryService.cs b/src/Workspaces/Core/Portable/Workspace/Host/CompilationFactory/ICompilationFactoryService.cs index 2140d43b72719..358cf9d40a6ae 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/CompilationFactory/ICompilationFactoryService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/CompilationFactory/ICompilationFactoryService.cs @@ -13,6 +13,6 @@ internal interface ICompilationFactoryService : ILanguageService Compilation CreateCompilation(string assemblyName, CompilationOptions options); Compilation CreateSubmissionCompilation(string assemblyName, CompilationOptions options, Type? hostObjectType); CompilationOptions GetDefaultCompilationOptions(); - GeneratorDriver? CreateGeneratorDriver(ParseOptions parseOptions, ImmutableArray generators, AnalyzerConfigOptionsProvider optionsProvider, ImmutableArray additionalTexts); + GeneratorDriver CreateGeneratorDriver(ParseOptions parseOptions, ImmutableArray generators, AnalyzerConfigOptionsProvider optionsProvider, ImmutableArray additionalTexts); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/EventListener/EventListenerMetadata.cs b/src/Workspaces/Core/Portable/Workspace/Host/EventListener/EventListenerMetadata.cs index 1d92e0042cdf6..51e54e6b4a3a7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/EventListener/EventListenerMetadata.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/EventListener/EventListenerMetadata.cs @@ -18,14 +18,14 @@ internal class EventListenerMetadata : WorkspaceKindMetadata { public string Service { get; } - public EventListenerMetadata(IDictionary data) : - base(data) + public EventListenerMetadata(IDictionary data) + : base(data) { this.Service = (string)data.GetValueOrDefault("Service"); } - public EventListenerMetadata(string service, params string[] workspaceKinds) : - base(workspaceKinds) + public EventListenerMetadata(string service, params string[] workspaceKinds) + : base(workspaceKinds) { if (workspaceKinds?.Length == 0) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalDocumentState.cs new file mode 100644 index 0000000000000..1216a5261dc7f --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalDocumentState.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + internal sealed class AdditionalDocumentState : TextDocumentState + { + private readonly AdditionalText _additionalText; + + private AdditionalDocumentState( + SolutionServices solutionServices, + IDocumentServiceProvider documentServiceProvider, + DocumentInfo.DocumentAttributes attributes, + SourceText? sourceText, + ValueSource textAndVersionSource) + : base(solutionServices, documentServiceProvider, attributes, sourceText, textAndVersionSource) + { + _additionalText = new AdditionalTextWithState(this); + } + + public AdditionalDocumentState( + DocumentInfo documentInfo, + SolutionServices solutionServices) + : base(documentInfo, solutionServices) + { + _additionalText = new AdditionalTextWithState(this); + } + + public AdditionalText AdditionalText => _additionalText; + + public new AdditionalDocumentState UpdateText(TextLoader loader, PreservationMode mode) + => (AdditionalDocumentState)base.UpdateText(loader, mode); + + public new AdditionalDocumentState UpdateText(SourceText text, PreservationMode mode) + => (AdditionalDocumentState)base.UpdateText(text, mode); + + public new AdditionalDocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode) + => (AdditionalDocumentState)base.UpdateText(newTextAndVersion, mode); + + protected override TextDocumentState UpdateText(ValueSource newTextSource, PreservationMode mode, bool incremental) + { + return new AdditionalDocumentState( + this.solutionServices, + this.Services, + this.Attributes, + this.sourceText, + newTextSource); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalTextWithState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalTextWithState.cs index ab23b1b4a901c..bd2c335acc41f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalTextWithState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AdditionalTextWithState.cs @@ -9,16 +9,16 @@ namespace Microsoft.CodeAnalysis.Diagnostics { /// - /// An implementation of for the compiler that wraps a . + /// An implementation of for the compiler that wraps a . /// internal sealed class AdditionalTextWithState : AdditionalText { - private readonly TextDocumentState _documentState; + private readonly AdditionalDocumentState _documentState; /// - /// Create a from a . + /// Create a from a . /// - public AdditionalTextWithState(TextDocumentState documentState) + public AdditionalTextWithState(AdditionalDocumentState documentState) => _documentState = documentState ?? throw new ArgumentNullException(nameof(documentState)); /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 7675cf7d47687..ef64b32826c87 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -36,13 +36,14 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1333566", AllowGenericEnumeration = false)] internal static async Task FindAsync( TextDocumentStates documentStates, HashSet searchingChecksumsLeft, Dictionary result, CancellationToken cancellationToken) where TState : TextDocumentState { - foreach (var state in documentStates.States) + foreach (var (_, state) in documentStates.States) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index 5f2e39a2f8fc6..2fcb8a0e4cba2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -277,7 +277,7 @@ public async ValueTask> GetSourceGeneratedD var generatedDocumentStates = await _solution.State.GetSourceGeneratedDocumentStatesAsync(this.State, cancellationToken).ConfigureAwait(false); // return an iterator to avoid eagerly allocating all the document instances - return generatedDocumentStates.States.Select(state => + return generatedDocumentStates.States.Values.Select(state => ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this)))!; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 4c96f8797db96..cbb722776a0bb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -35,7 +35,7 @@ internal partial class ProjectState /// The additional documents in this project. They are sorted by to provide a stable sort for /// . /// - public readonly TextDocumentStates AdditionalDocumentStates; + public readonly TextDocumentStates AdditionalDocumentStates; /// /// The analyzer config documents in this project. They are sorted by to provide a stable sort for @@ -66,7 +66,7 @@ private ProjectState( HostLanguageServices languageServices, SolutionServices solutionServices, TextDocumentStates documentStates, - TextDocumentStates additionalDocumentStates, + TextDocumentStates additionalDocumentStates, TextDocumentStates analyzerConfigDocumentStates, AsyncLazy lazyLatestDocumentVersion, AsyncLazy lazyLatestDocumentTopLevelChangeVersion, @@ -116,7 +116,7 @@ public ProjectState(ProjectInfo projectInfo, HostLanguageServices languageServic var parseOptions = projectInfoFixed.ParseOptions; DocumentStates = new TextDocumentStates(projectInfoFixed.Documents, info => CreateDocument(info, parseOptions)); - AdditionalDocumentStates = new TextDocumentStates(projectInfoFixed.AdditionalDocuments, info => new TextDocumentState(info, solutionServices)); + AdditionalDocumentStates = new TextDocumentStates(projectInfoFixed.AdditionalDocuments, info => new AdditionalDocumentState(info, solutionServices)); _lazyLatestDocumentVersion = new AsyncLazy(c => ComputeLatestDocumentVersionAsync(DocumentStates, AdditionalDocumentStates, c), cacheResult: true); _lazyLatestDocumentTopLevelChangeVersion = new AsyncLazy(c => ComputeLatestDocumentTopLevelChangeVersionAsync(DocumentStates, AdditionalDocumentStates, c), cacheResult: true); @@ -161,11 +161,11 @@ private ProjectInfo FixProjectInfo(ProjectInfo projectInfo) return projectInfo; } - private static async Task ComputeLatestDocumentVersionAsync(TextDocumentStates documentStates, TextDocumentStates additionalDocumentStates, CancellationToken cancellationToken) + private static async Task ComputeLatestDocumentVersionAsync(TextDocumentStates documentStates, TextDocumentStates additionalDocumentStates, CancellationToken cancellationToken) { // this may produce a version that is out of sync with the actual Document versions. var latestVersion = VersionStamp.Default; - foreach (var state in documentStates.States) + foreach (var (_, state) in documentStates.States) { cancellationToken.ThrowIfCancellationRequested(); @@ -176,7 +176,7 @@ private static async Task ComputeLatestDocumentVersionAsync(TextDo } } - foreach (var state in additionalDocumentStates.States) + foreach (var (_, state) in additionalDocumentStates.States) { cancellationToken.ThrowIfCancellationRequested(); @@ -190,7 +190,7 @@ private static async Task ComputeLatestDocumentVersionAsync(TextDo private AsyncLazy CreateLazyLatestDocumentTopLevelChangeVersion( TextDocumentState newDocument, TextDocumentStates newDocumentStates, - TextDocumentStates newAdditionalDocumentStates) + TextDocumentStates newAdditionalDocumentStates) { if (_lazyLatestDocumentTopLevelChangeVersion.TryGetValue(out var oldVersion)) { @@ -208,11 +208,11 @@ private static async Task ComputeTopLevelChangeTextVersionAsync(Ve return newVersion.GetNewerVersion(oldVersion); } - private static async Task ComputeLatestDocumentTopLevelChangeVersionAsync(TextDocumentStates documentStates, TextDocumentStates additionalDocumentStates, CancellationToken cancellationToken) + private static async Task ComputeLatestDocumentTopLevelChangeVersionAsync(TextDocumentStates documentStates, TextDocumentStates additionalDocumentStates, CancellationToken cancellationToken) { // this may produce a version that is out of sync with the actual Document versions. var latestVersion = VersionStamp.Default; - foreach (var state in documentStates.States) + foreach (var (_, state) in documentStates.States) { cancellationToken.ThrowIfCancellationRequested(); @@ -220,7 +220,7 @@ private static async Task ComputeLatestDocumentTopLevelChangeVersi latestVersion = version.GetNewerVersion(latestVersion); } - foreach (var state in additionalDocumentStates.States) + foreach (var (_, state) in additionalDocumentStates.States) { cancellationToken.ThrowIfCancellationRequested(); @@ -245,7 +245,7 @@ internal DocumentState CreateDocument(DocumentInfo documentInfo, ParseOptions? p public AnalyzerOptions AnalyzerOptions => _lazyAnalyzerOptions ??= new AnalyzerOptions( - additionalFiles: AdditionalDocumentStates.SelectAsArray(static state => new AdditionalTextWithState(state)), + additionalFiles: AdditionalDocumentStates.SelectAsArray(static documentState => documentState.AdditionalText), optionsProvider: new WorkspaceAnalyzerConfigOptionsProvider(this)); public async Task> GetAnalyzerOptionsForPathAsync( @@ -364,7 +364,7 @@ private static ValueSource ComputeAnalyzerConfigSetVal return new AsyncLazy( asynchronousComputeFunction: async cancellationToken => { - var tasks = analyzerConfigDocumentStates.States.Select(a => a.GetAnalyzerConfigAsync(cancellationToken)); + var tasks = analyzerConfigDocumentStates.States.Values.Select(a => a.GetAnalyzerConfigAsync(cancellationToken)); var analyzerConfigs = await Task.WhenAll(tasks).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -461,7 +461,7 @@ public async Task GetSemanticVersionAsync(CancellationToken cancel private ProjectState With( ProjectInfo? projectInfo = null, TextDocumentStates? documentStates = null, - TextDocumentStates? additionalDocumentStates = null, + TextDocumentStates? additionalDocumentStates = null, TextDocumentStates? analyzerConfigDocumentStates = null, AsyncLazy? latestDocumentVersion = null, AsyncLazy? latestDocumentTopLevelChangeVersion = null, @@ -609,7 +609,7 @@ public ProjectState AddDocuments(ImmutableArray documents) documentStates: DocumentStates.AddRange(documents)); } - public ProjectState AddAdditionalDocuments(ImmutableArray documents) + public ProjectState AddAdditionalDocuments(ImmutableArray documents) { Debug.Assert(!documents.Any(d => AdditionalDocumentStates.Contains(d.Id))); @@ -699,7 +699,7 @@ public ProjectState UpdateDocument(DocumentState newDocument, bool textChanged, latestDocumentTopLevelChangeVersion: dependentSemanticVersion); } - public ProjectState UpdateAdditionalDocument(TextDocumentState newDocument, bool textChanged, bool recalculateDependentVersions) + public ProjectState UpdateAdditionalDocument(AdditionalDocumentState newDocument, bool textChanged, bool recalculateDependentVersions) { var oldDocument = AdditionalDocumentStates.GetRequiredState(newDocument.Id); if (oldDocument == newDocument) @@ -745,7 +745,7 @@ public ProjectState UpdateDocumentsOrder(ImmutableList documentIds) private void GetLatestDependentVersions( TextDocumentStates newDocumentStates, - TextDocumentStates newAdditionalDocumentStates, + TextDocumentStates newAdditionalDocumentStates, TextDocumentState oldDocument, TextDocumentState newDocument, bool recalculateDependentVersions, bool textChanged, out AsyncLazy dependentDocumentVersion, out AsyncLazy dependentSemanticVersion) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs index 3af5e1a6c1c03..92e824fcc101b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs @@ -9,6 +9,10 @@ namespace Microsoft.CodeAnalysis { internal partial class SolutionState { + /// + /// Represents a change that needs to be made to a , , or both in response to + /// some user edit. + /// private abstract partial class CompilationAndGeneratorDriverTranslationAction { public virtual Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) @@ -28,6 +32,8 @@ public virtual Task TransformCompilationAsync(Compilation oldCompil /// public abstract bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput { get; } + public virtual GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) => generatorDriver; + /// /// When changes are made to a solution, we make a list of translation actions. If multiple similar changes happen in rapid /// succession, we may be able to merge them without holding onto intermediate state. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs index 1ae69d5f48c37..c316cc133c361 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs @@ -4,9 +4,12 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { @@ -50,13 +53,10 @@ public override Task TransformCompilationAsync(Compilation oldCompi internal sealed class TouchAdditionalDocumentAction : CompilationAndGeneratorDriverTranslationAction { -#pragma warning disable IDE0052 // Remove unread private members - // https://github.com/dotnet/roslyn/issues/44161: right now there is no way to tell a GeneratorDriver that an additional document changed - private readonly TextDocumentState _oldState; - private readonly TextDocumentState _newState; -#pragma warning restore IDE0052 // Remove unread private members + private readonly AdditionalDocumentState _oldState; + private readonly AdditionalDocumentState _newState; - public TouchAdditionalDocumentAction(TextDocumentState oldState, TextDocumentState newState) + public TouchAdditionalDocumentAction(AdditionalDocumentState oldState, AdditionalDocumentState newState) { _oldState = oldState; _newState = newState; @@ -77,6 +77,14 @@ public TouchAdditionalDocumentAction(TextDocumentState oldState, TextDocumentSta return null; } + + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + var oldText = _oldState.AdditionalText; + var newText = _newState.AdditionalText; + + return generatorDriver.ReplaceAdditionalText(oldText, newText); + } } internal sealed class RemoveDocumentsAction : CompilationAndGeneratorDriverTranslationAction @@ -132,10 +140,12 @@ public override async Task TransformCompilationAsync(Compilation ol internal sealed class ReplaceAllSyntaxTreesAction : CompilationAndGeneratorDriverTranslationAction { private readonly ProjectState _state; + private readonly bool _isParseOptionChange; - public ReplaceAllSyntaxTreesAction(ProjectState state) + public ReplaceAllSyntaxTreesAction(ProjectState state, bool isParseOptionChange) { _state = state; + _isParseOptionChange = isParseOptionChange; } public override async Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) @@ -153,25 +163,57 @@ public override async Task TransformCompilationAsync(Compilation ol // Because this removes all trees, it'd also remove the generated trees. public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => false; + + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + if (_isParseOptionChange) + { + RoslynDebug.AssertNotNull(_state.ParseOptions); + return generatorDriver.WithUpdatedParseOptions(_state.ParseOptions); + } + else + { + // We are using this as a way to reorder syntax trees -- we don't need to do anything as the driver + // will get the new compilation once we pass it to it. + return generatorDriver; + } + } } internal sealed class ProjectCompilationOptionsAction : CompilationAndGeneratorDriverTranslationAction { - private readonly CompilationOptions _options; + private readonly ProjectState _state; + private readonly bool _isAnalyzerConfigChange; - public ProjectCompilationOptionsAction(CompilationOptions options) + public ProjectCompilationOptionsAction(ProjectState state, bool isAnalyzerConfigChange) { - _options = options; + _state = state; + _isAnalyzerConfigChange = isAnalyzerConfigChange; } public override Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) { - return Task.FromResult(oldCompilation.WithOptions(_options)); + RoslynDebug.AssertNotNull(_state.CompilationOptions); + return Task.FromResult(oldCompilation.WithOptions(_state.CompilationOptions)); } // Updating the options of a compilation doesn't require us to reparse trees, so we can use this to update // compilations with stale generated trees. public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + if (_isAnalyzerConfigChange) + { + return generatorDriver.WithUpdatedAnalyzerConfigOptions(_state.AnalyzerOptions.AnalyzerConfigOptionsProvider); + } + else + { + // Changing any other option is fine and the driver can be reused. The driver + // will get the new compilation once we pass it to it. + return generatorDriver; + } + } } internal sealed class ProjectAssemblyNameAction : CompilationAndGeneratorDriverTranslationAction @@ -195,11 +237,8 @@ public override Task TransformCompilationAsync(Compilation oldCompi internal sealed class AddAnalyzerReferencesAction : CompilationAndGeneratorDriverTranslationAction { -#pragma warning disable IDE0052 // Remove unread private members - // https://github.com/dotnet/roslyn/issues/44161: right now there is no way to tell a GeneratorDriver that an analyzer reference has been added private readonly ImmutableArray _analyzerReferences; private readonly string _language; -#pragma warning restore IDE0052 // Remove unread private members public AddAnalyzerReferencesAction(ImmutableArray analyzerReferences, string language) { @@ -211,15 +250,17 @@ public AddAnalyzerReferencesAction(ImmutableArray analyzerRef // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping // the compilation with stale trees around, answering true is still important. public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + return generatorDriver.AddGenerators(_analyzerReferences.SelectMany(r => r.GetGenerators(_language)).ToImmutableArray()); + } } internal sealed class RemoveAnalyzerReferencesAction : CompilationAndGeneratorDriverTranslationAction { -#pragma warning disable IDE0052 // Remove unread private members - // https://github.com/dotnet/roslyn/issues/44161: right now there is no way to tell a GeneratorDriver that an analyzer reference has been removed private readonly ImmutableArray _analyzerReferences; private readonly string _language; -#pragma warning restore IDE0052 // Remove unread private members public RemoveAnalyzerReferencesAction(ImmutableArray analyzerReferences, string language) { @@ -231,16 +272,17 @@ public RemoveAnalyzerReferencesAction(ImmutableArray analyzer // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping // the compilation with stale trees around, answering true is still important. public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + return generatorDriver.RemoveGenerators(_analyzerReferences.SelectMany(r => r.GetGenerators(_language)).ToImmutableArray()); + } } internal sealed class AddAdditionalDocumentsAction : CompilationAndGeneratorDriverTranslationAction { -#pragma warning disable IDE0052 // Remove unread private members - // https://github.com/dotnet/roslyn/issues/44161: right now there is no way to tell a GeneratorDriver that an additional file has been added - private readonly ImmutableArray _additionalDocuments; -#pragma warning restore IDE0052 // Remove unread private members + private readonly ImmutableArray _additionalDocuments; - public AddAdditionalDocumentsAction(ImmutableArray additionalDocuments) + public AddAdditionalDocumentsAction(ImmutableArray additionalDocuments) { _additionalDocuments = additionalDocuments; } @@ -249,16 +291,18 @@ public AddAdditionalDocumentsAction(ImmutableArray additional // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping // the compilation with stale trees around, answering true is still important. public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + return generatorDriver.AddAdditionalTexts(_additionalDocuments.SelectAsArray(static documentState => documentState.AdditionalText)); + } } internal sealed class RemoveAdditionalDocumentsAction : CompilationAndGeneratorDriverTranslationAction { -#pragma warning disable IDE0052 // Remove unread private members - // https://github.com/dotnet/roslyn/issues/44161: right now there is no way to tell a GeneratorDriver that an additional file has been added - private readonly ImmutableArray _additionalDocuments; -#pragma warning restore IDE0052 // Remove unread private members + private readonly ImmutableArray _additionalDocuments; - public RemoveAdditionalDocumentsAction(ImmutableArray additionalDocuments) + public RemoveAdditionalDocumentsAction(ImmutableArray additionalDocuments) { _additionalDocuments = additionalDocuments; } @@ -267,6 +311,11 @@ public RemoveAdditionalDocumentsAction(ImmutableArray additio // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping // the compilation with stale trees around, answering true is still important. public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + + public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver) + { + return generatorDriver.RemoveAdditionalTexts(_additionalDocuments.SelectAsArray(static documentState => documentState.AdditionalText)); + } } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index 96ee1b8160480..b03e5a92a2f8a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -31,7 +31,8 @@ private class State compilationWithoutGeneratedDocuments: null, declarationOnlyCompilation: null, generatedDocuments: TextDocumentStates.Empty, - generatedDocumentsAreFinal: false); + generatedDocumentsAreFinal: false, + generatorDriver: null); /// /// A strong reference to the declaration-only compilation. This compilation isn't used to produce symbols, @@ -53,6 +54,13 @@ private class State /// public TextDocumentStates GeneratedDocuments { get; } + /// + /// The that was used for the last run, to allow for incremental reuse. May be null + /// if we don't have generators in the first place, haven't ran generators yet for this project, or had to get rid of our + /// driver for some reason. + /// + public GeneratorDriver? GeneratorDriver { get; } + /// /// Whether the generated documents in are final and should not be regenerated. It's important /// that once we've ran generators once we don't want to run them again. Once we've ran them the first time, those syntax trees @@ -78,6 +86,7 @@ protected State( ValueSource>? compilationWithoutGeneratedDocuments, Compilation? declarationOnlyCompilation, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, bool generatedDocumentsAreFinal) { // Declaration-only compilations should never have any references @@ -86,12 +95,14 @@ protected State( CompilationWithoutGeneratedDocuments = compilationWithoutGeneratedDocuments; DeclarationOnlyCompilation = declarationOnlyCompilation; GeneratedDocuments = generatedDocuments; + GeneratorDriver = generatorDriver; GeneratedDocumentsAreFinal = generatedDocumentsAreFinal; } public static State Create( Compilation compilation, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, Compilation? compilationWithGeneratedDocuments, ImmutableArray> intermediateProjects) { @@ -101,8 +112,8 @@ public static State Create( // DeclarationState now. We'll pass false for generatedDocumentsAreFinal because this is being called // if our referenced projects are changing, so we'll have to rerun to consume changes. return intermediateProjects.Length == 0 - ? new FullDeclarationState(compilation, generatedDocuments, generatedDocumentsAreFinal: false) - : (State)new InProgressState(compilation, generatedDocuments, compilationWithGeneratedDocuments, intermediateProjects); + ? new FullDeclarationState(compilation, generatedDocuments, generatorDriver, generatedDocumentsAreFinal: false) + : new InProgressState(compilation, generatedDocuments, generatorDriver, compilationWithGeneratedDocuments, intermediateProjects); } public static ValueSource> CreateValueSource( @@ -138,11 +149,13 @@ private sealed class InProgressState : State public InProgressState( Compilation inProgressCompilation, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, Compilation? compilationWithGeneratedDocuments, ImmutableArray<(ProjectState state, CompilationAndGeneratorDriverTranslationAction action)> intermediateProjects) : base(compilationWithoutGeneratedDocuments: new ConstantValueSource>(inProgressCompilation), declarationOnlyCompilation: null, generatedDocuments, + generatorDriver, generatedDocumentsAreFinal: false) // since we have a set of transformations to make, we'll always have to run generators again { Contract.ThrowIfTrue(intermediateProjects.IsDefault); @@ -160,10 +173,12 @@ private sealed class LightDeclarationState : State { public LightDeclarationState(Compilation declarationOnlyCompilation, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, bool generatedDocumentsAreFinal) : base(compilationWithoutGeneratedDocuments: null, declarationOnlyCompilation, generatedDocuments, + generatorDriver, generatedDocumentsAreFinal) { } @@ -177,10 +192,12 @@ private sealed class FullDeclarationState : State { public FullDeclarationState(Compilation declarationCompilation, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, bool generatedDocumentsAreFinal) : base(new WeakValueSource(declarationCompilation), declarationCompilation.Clone().RemoveAllReferences(), generatedDocuments, + generatorDriver, generatedDocumentsAreFinal) { } @@ -224,10 +241,12 @@ private FinalState( Compilation compilationWithoutGeneratedFiles, bool hasSuccessfullyLoaded, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, UnrootedSymbolSet unrootedSymbolSet) : base(compilationWithoutGeneratedFilesSource, compilationWithoutGeneratedFiles.Clone().RemoveAllReferences(), generatedDocuments, + generatorDriver: generatorDriver, generatedDocumentsAreFinal: true) // when we're in a final state, we've ran generators and should not run again { HasSuccessfullyLoaded = hasSuccessfullyLoaded; @@ -252,6 +271,7 @@ public static FinalState Create( Compilation compilationWithoutGeneratedFiles, bool hasSuccessfullyLoaded, TextDocumentStates generatedDocuments, + GeneratorDriver? generatorDriver, Compilation finalCompilation, ProjectId projectId, Dictionary? metadataReferenceToProjectId) @@ -267,7 +287,9 @@ public static FinalState Create( compilationWithoutGeneratedFilesSource, compilationWithoutGeneratedFiles, hasSuccessfullyLoaded, - generatedDocuments, unrootedSymbolSet); + generatedDocuments, + generatorDriver, + unrootedSymbolSet); } private static void RecordAssemblySymbols(ProjectId projectId, Compilation compilation, Dictionary? metadataReferenceToProjectId) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 1ccf65b54804c..eaab5fc8f7178 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -155,7 +155,7 @@ public ICompilationTracker Fork( } } - var newState = State.Create(newInProgressCompilation, state.GeneratedDocuments, state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects); + var newState = State.Create(newInProgressCompilation, state.GeneratedDocuments, state.GeneratorDriver, state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects); return new CompilationTracker(newProject, newState); } @@ -166,10 +166,10 @@ public ICompilationTracker Fork( if (translate != null) { var intermediateProjects = ImmutableArray.Create((this.ProjectState, translate)); - return new CompilationTracker(newProject, new InProgressState(declarationOnlyCompilation, state.GeneratedDocuments, compilationWithGeneratedDocuments: state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects)); + return new CompilationTracker(newProject, new InProgressState(declarationOnlyCompilation, state.GeneratedDocuments, state.GeneratorDriver, compilationWithGeneratedDocuments: state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects)); } - return new CompilationTracker(newProject, new LightDeclarationState(declarationOnlyCompilation, state.GeneratedDocuments, generatedDocumentsAreFinal: false)); + return new CompilationTracker(newProject, new LightDeclarationState(declarationOnlyCompilation, state.GeneratedDocuments, state.GeneratorDriver, generatedDocumentsAreFinal: false)); } // We have nothing. Just make a tracker that only points to the new project. We'll have @@ -188,7 +188,7 @@ public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, Do GetPartialCompilationState( solution, docState.Id, out var inProgressProject, out var inProgressCompilation, - out var sourceGeneratedDocuments, out var metadataReferenceToProjectId, cancellationToken); + out var sourceGeneratedDocuments, out var generatorDriver, out var metadataReferenceToProjectId, cancellationToken); if (!inProgressCompilation.SyntaxTrees.Contains(tree)) { @@ -215,6 +215,7 @@ public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, Do inProgressCompilation, hasSuccessfullyLoaded: false, sourceGeneratedDocuments, + generatorDriver, inProgressCompilation, this.ProjectState.Id, metadataReferenceToProjectId); @@ -238,6 +239,7 @@ private void GetPartialCompilationState( out ProjectState inProgressProject, out Compilation inProgressCompilation, out TextDocumentStates sourceGeneratedDocuments, + out GeneratorDriver? generatorDriver, out Dictionary? metadataReferenceToProjectId, CancellationToken cancellationToken) { @@ -248,6 +250,7 @@ private void GetPartialCompilationState( var inProgressState = state as InProgressState; sourceGeneratedDocuments = state.GeneratedDocuments; + generatorDriver = state.GeneratorDriver; // all changes left for this document is modifying the given document. // we can use current state as it is since we will replace the document with latest document anyway. @@ -259,7 +262,7 @@ private void GetPartialCompilationState( // We'll add in whatever generated documents we do have; these may be from a prior run prior to some changes // being made to the project, but it's the best we have so we'll use it. - inProgressCompilation = compilationWithoutGeneratedDocuments.AddSyntaxTrees(sourceGeneratedDocuments.States.Select(state => state.GetSyntaxTree(cancellationToken))); + inProgressCompilation = compilationWithoutGeneratedDocuments.AddSyntaxTrees(sourceGeneratedDocuments.States.Values.Select(state => state.GetSyntaxTree(cancellationToken))); // This is likely a bug. It seems possible to pass out a partial compilation state that we don't // properly record assembly symbols for. @@ -302,7 +305,7 @@ private void GetPartialCompilationState( inProgressCompilation = compilationWithoutGeneratedDocuments; } - inProgressCompilation = inProgressCompilation.AddSyntaxTrees(sourceGeneratedDocuments.States.Select(state => state.GetSyntaxTree(cancellationToken))); + inProgressCompilation = inProgressCompilation.AddSyntaxTrees(sourceGeneratedDocuments.States.Values.Select(state => state.GetSyntaxTree(cancellationToken))); // Now add in back a consistent set of project references. For project references // try to get either a CompilationReference or a SkeletonReference. This ensures @@ -429,7 +432,7 @@ private async Task GetOrBuildDeclarationCompilationAsync(SolutionSe // okay, move to full declaration state. do this so that declaration only compilation never // realize symbols. var declarationOnlyCompilation = state.DeclarationOnlyCompilation.Clone(); - WriteState(new FullDeclarationState(declarationOnlyCompilation, state.GeneratedDocuments, state.GeneratedDocumentsAreFinal), solutionServices); + WriteState(new FullDeclarationState(declarationOnlyCompilation, state.GeneratedDocuments, state.GeneratorDriver, state.GeneratedDocumentsAreFinal), solutionServices); return declarationOnlyCompilation; } @@ -443,7 +446,7 @@ private async Task GetOrBuildDeclarationCompilationAsync(SolutionSe return compilation; } - (compilation, _) = await BuildDeclarationCompilationFromInProgressAsync(solutionServices, (InProgressState)state, compilation, cancellationToken).ConfigureAwait(false); + (compilation, _, _) = await BuildDeclarationCompilationFromInProgressAsync(solutionServices, (InProgressState)state, compilation, cancellationToken).ConfigureAwait(false); // We must have an in progress compilation. Build off of that. return compilation; @@ -525,8 +528,8 @@ private Task BuildCompilationInfoAsync( // so we can pass those to FinalizeCompilationAsync to avoid the recomputation. This is necessary for correctness as otherwise // we'd be reparsing trees which could result in generated documents changing identity. var authoritativeGeneratedDocuments = state.GeneratedDocumentsAreFinal ? state.GeneratedDocuments : (TextDocumentStates?)null; - var nonAuthoritativeGeneratedDocuments = state.GeneratedDocuments; + var generatorDriver = state.GeneratorDriver; if (compilation == null) { @@ -535,7 +538,7 @@ private Task BuildCompilationInfoAsync( if (state.DeclarationOnlyCompilation != null) { // we have declaration only compilation. build final one from it. - return FinalizeCompilationAsync(solution, state.DeclarationOnlyCompilation, authoritativeGeneratedDocuments, nonAuthoritativeGeneratedDocuments, compilationWithStaleGeneratedTrees: null, cancellationToken); + return FinalizeCompilationAsync(solution, state.DeclarationOnlyCompilation, authoritativeGeneratedDocuments, nonAuthoritativeGeneratedDocuments, compilationWithStaleGeneratedTrees: null, generatorDriver, cancellationToken); } // We've got nothing. Build it from scratch :( @@ -551,6 +554,7 @@ private Task BuildCompilationInfoAsync( authoritativeGeneratedDocuments, nonAuthoritativeGeneratedDocuments, compilationWithStaleGeneratedTrees: null, + generatorDriver, cancellationToken); } else @@ -572,6 +576,7 @@ private async Task BuildCompilationInfoFromScratchAsync( authoritativeGeneratedDocuments: null, nonAuthoritativeGeneratedDocuments: TextDocumentStates.Empty, compilationWithStaleGeneratedTrees: null, + generatorDriver: null, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) @@ -601,7 +606,7 @@ private async Task BuildDeclarationCompilationFromScratchAsync( compilation = compilation.AddSyntaxTrees(trees); trees.Free(); - WriteState(new FullDeclarationState(compilation, TextDocumentStates.Empty, generatedDocumentsAreFinal: false), solutionServices); + WriteState(new FullDeclarationState(compilation, TextDocumentStates.Empty, generatorDriver: null, generatedDocumentsAreFinal: false), solutionServices); return compilation; } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) @@ -634,8 +639,15 @@ private async Task BuildFinalStateFromInProgressStateAsync( { try { - var (compilationWithoutGenerators, compilationWithGenerators) = await BuildDeclarationCompilationFromInProgressAsync(solution.Services, state, inProgressCompilation, cancellationToken).ConfigureAwait(false); - return await FinalizeCompilationAsync(solution, compilationWithoutGenerators, authoritativeGeneratedDocuments: null, nonAuthoritativeGeneratedDocuments: state.GeneratedDocuments, compilationWithGenerators, cancellationToken).ConfigureAwait(false); + var (compilationWithoutGenerators, compilationWithGenerators, generatorDriver) = await BuildDeclarationCompilationFromInProgressAsync(solution.Services, state, inProgressCompilation, cancellationToken).ConfigureAwait(false); + return await FinalizeCompilationAsync( + solution, + compilationWithoutGenerators, + authoritativeGeneratedDocuments: null, + nonAuthoritativeGeneratedDocuments: state.GeneratedDocuments, + compilationWithGenerators, + generatorDriver, + cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -643,12 +655,13 @@ private async Task BuildFinalStateFromInProgressStateAsync( } } - private async Task<(Compilation compilationWithoutGenerators, Compilation? compilationWithGenerators)> BuildDeclarationCompilationFromInProgressAsync( + private async Task<(Compilation compilationWithoutGenerators, Compilation? compilationWithGenerators, GeneratorDriver? generatorDriver)> BuildDeclarationCompilationFromInProgressAsync( SolutionServices solutionServices, InProgressState state, Compilation compilationWithoutGenerators, CancellationToken cancellationToken) { try { var compilationWithGenerators = state.CompilationWithGeneratedDocuments; + var generatorDriver = state.GeneratorDriver; // If compilationWithGenerators is the same as compilationWithoutGenerators, then it means a prior run of generators // didn't produce any files. In that case, we'll just make compilationWithGenerators null so we avoid doing any @@ -686,14 +699,19 @@ private async Task BuildFinalStateFromInProgressStateAsync( } } + if (generatorDriver != null) + { + generatorDriver = intermediateProject.action.TransformGeneratorDriver(generatorDriver); + } + // We have updated state, so store this new result; this allows us to drop the intermediate state we already processed // even if we were to get cancelled at a later point. intermediateProjects = intermediateProjects.RemoveAt(0); - this.WriteState(State.Create(compilationWithoutGenerators, state.GeneratedDocuments, compilationWithGenerators, intermediateProjects), solutionServices); + this.WriteState(State.Create(compilationWithoutGenerators, state.GeneratedDocuments, generatorDriver, compilationWithGenerators, intermediateProjects), solutionServices); } - return (compilationWithoutGenerators, compilationWithGenerators); + return (compilationWithoutGenerators, compilationWithGenerators, generatorDriver); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -726,12 +744,19 @@ public CompilationInfo(Compilation compilation, bool hasSuccessfullyLoaded, Text /// The generated documents from a previous pass which may /// or may not be correct for the current compilation. These states may be used to access cached results, if /// and when applicable for the current compilation. + /// The compilation from a prior run that contains generated trees, which + /// match the states included in . If a generator run here produces + /// the same set of generated documents as are in , and we don't need to make any other + /// changes to references, we can then use this compilation instead of re-adding source generated files again to the + /// . + /// The generator driver that can be reused for this finalization. private async Task FinalizeCompilationAsync( SolutionState solution, Compilation compilationWithoutGenerators, TextDocumentStates? authoritativeGeneratedDocuments, TextDocumentStates nonAuthoritativeGeneratedDocuments, Compilation? compilationWithStaleGeneratedTrees, + GeneratorDriver? generatorDriver, CancellationToken cancellationToken) { try @@ -812,7 +837,7 @@ private async Task FinalizeCompilationAsync( { generatedDocuments = authoritativeGeneratedDocuments.Value; compilationWithGenerators = compilationWithoutGenerators.AddSyntaxTrees( - await generatedDocuments.States.SelectAsArrayAsync(state => state.GetSyntaxTreeAsync(cancellationToken)).ConfigureAwait(false)); + await generatedDocuments.States.Values.SelectAsArrayAsync(state => state.GetSyntaxTreeAsync(cancellationToken)).ConfigureAwait(false)); } else { @@ -820,76 +845,77 @@ private async Task FinalizeCompilationAsync( if (ProjectState.SourceGenerators.Any()) { - var additionalTexts = this.ProjectState.AdditionalDocumentStates.SelectAsArray(state => new AdditionalTextWithState(state)); - var compilationFactory = this.ProjectState.LanguageServices.GetRequiredService(); + // If we don't already have a generator driver, we'll have to create one from scratch + if (generatorDriver == null) + { + var additionalTexts = this.ProjectState.AdditionalDocumentStates.SelectAsArray(static documentState => documentState.AdditionalText); + var compilationFactory = this.ProjectState.LanguageServices.GetRequiredService(); + + generatorDriver = compilationFactory.CreateGeneratorDriver( + this.ProjectState.ParseOptions!, + ProjectState.SourceGenerators, + this.ProjectState.AnalyzerOptions.AnalyzerConfigOptionsProvider, + additionalTexts); + } - var generatorDriver = compilationFactory.CreateGeneratorDriver( - this.ProjectState.ParseOptions!, - ProjectState.SourceGenerators, - this.ProjectState.AnalyzerOptions.AnalyzerConfigOptionsProvider, - additionalTexts); + generatorDriver = generatorDriver.RunGenerators(compilationWithoutGenerators, cancellationToken); + var runResult = generatorDriver.GetRunResult(); - if (generatorDriver != null) + // We may be able to reuse compilationWithStaleGeneratedTrees if the generated trees are identical. We will assign null + // to compilationWithStaleGeneratedTrees if we at any point realize it can't be used. We'll first check the count of trees + // if that changed then we absolutely can't reuse it. But if the counts match, we'll then see if each generated tree + // content is identical to the prior generation run; if we find a match each time, then the set of the generated trees + // and the prior generated trees are identical. + if (compilationWithStaleGeneratedTrees != null) { - generatorDriver = generatorDriver.RunGenerators(compilationWithoutGenerators, cancellationToken); - var runResult = generatorDriver.GetRunResult(); - - // We may be able to reuse compilationWithStaleGeneratedTrees if the generated trees are identical. We will assign null - // to compilationWithStaleGeneratedTrees if we at any point realize it can't be used. We'll first check the count of trees - // if that changed then we absolutely can't reuse it. But if the counts match, we'll then see if each generated tree - // content is identical to the prior generation run; if we find a match each time, then the set of the generated trees - // and the prior generated trees are identical. - if (compilationWithStaleGeneratedTrees != null) + if (nonAuthoritativeGeneratedDocuments.Count != runResult.Results.Sum(r => r.GeneratedSources.Length)) { - if (nonAuthoritativeGeneratedDocuments.Count != runResult.Results.Sum(r => r.GeneratedSources.Length)) - { - compilationWithStaleGeneratedTrees = null; - } + compilationWithStaleGeneratedTrees = null; } + } - foreach (var generatorResult in runResult.Results) + foreach (var generatorResult in runResult.Results) + { + foreach (var generatedSource in generatorResult.GeneratedSources) { - foreach (var generatedSource in generatorResult.GeneratedSources) + var existing = FindExistingGeneratedDocumentState( + nonAuthoritativeGeneratedDocuments, + generatorResult.Generator, + generatedSource.HintName); + + if (existing != null) { - var existing = FindExistingGeneratedDocumentState( - nonAuthoritativeGeneratedDocuments, - generatorResult.Generator, - generatedSource.HintName); - - if (existing != null) - { - var newDocument = existing.WithUpdatedGeneratedContent( - generatedSource.SourceText, - this.ProjectState.ParseOptions!); - - generatedDocumentsBuilder.Add(newDocument); - - if (newDocument != existing) - compilationWithStaleGeneratedTrees = null; - } - else - { - // NOTE: the use of generatedSource.SyntaxTree to fetch the path and options is OK, - // since the tree is a lazy tree and that won't trigger the parse. - var identity = SourceGeneratedDocumentIdentity.Generate( - ProjectState.Id, - generatedSource.HintName, - generatorResult.Generator, - generatedSource.SyntaxTree.FilePath); - - generatedDocumentsBuilder.Add( - SourceGeneratedDocumentState.Create( - identity, - generatedSource.SourceText, - generatedSource.SyntaxTree.Options, - this.ProjectState.LanguageServices, - solution.Services)); - - // The count of trees was the same, but something didn't match up. Since we're here, at least one tree - // was added, and an equal number must have been removed. Rather than trying to incrementally update - // this compilation, we'll just toss this and re-add all the trees. + var newDocument = existing.WithUpdatedGeneratedContent( + generatedSource.SourceText, + this.ProjectState.ParseOptions!); + + generatedDocumentsBuilder.Add(newDocument); + + if (newDocument != existing) compilationWithStaleGeneratedTrees = null; - } + } + else + { + // NOTE: the use of generatedSource.SyntaxTree to fetch the path and options is OK, + // since the tree is a lazy tree and that won't trigger the parse. + var identity = SourceGeneratedDocumentIdentity.Generate( + ProjectState.Id, + generatedSource.HintName, + generatorResult.Generator, + generatedSource.SyntaxTree.FilePath); + + generatedDocumentsBuilder.Add( + SourceGeneratedDocumentState.Create( + identity, + generatedSource.SourceText, + generatedSource.SyntaxTree.Options, + this.ProjectState.LanguageServices, + solution.Services)); + + // The count of trees was the same, but something didn't match up. Since we're here, at least one tree + // was added, and an equal number must have been removed. Rather than trying to incrementally update + // this compilation, we'll just toss this and re-add all the trees. + compilationWithStaleGeneratedTrees = null; } } } @@ -905,7 +931,7 @@ private async Task FinalizeCompilationAsync( { generatedDocuments = new TextDocumentStates(generatedDocumentsBuilder.ToImmutableAndClear()); compilationWithGenerators = compilationWithoutGenerators.AddSyntaxTrees( - await generatedDocuments.States.SelectAsArrayAsync(state => state.GetSyntaxTreeAsync(cancellationToken)).ConfigureAwait(false)); + await generatedDocuments.States.Values.SelectAsArrayAsync(state => state.GetSyntaxTreeAsync(cancellationToken)).ConfigureAwait(false)); } } @@ -915,6 +941,7 @@ private async Task FinalizeCompilationAsync( compilationWithoutGenerators, hasSuccessfullyLoaded, generatedDocuments, + generatorDriver, compilationWithGenerators, this.ProjectState.Id, metadataReferenceToProjectId); @@ -937,7 +964,7 @@ private async Task FinalizeCompilationAsync( var generatorAssemblyName = SourceGeneratedDocumentIdentity.GetGeneratorAssemblyName(generator); var generatorTypeName = SourceGeneratedDocumentIdentity.GetGeneratorTypeName(generator); - foreach (var state in states.States) + foreach (var (_, state) in states.States) { if (state.SourceGeneratorAssemblyName != generatorAssemblyName) continue; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index fcd5eab374c23..a758bd488a334 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -41,6 +41,7 @@ internal partial class SolutionState private readonly SolutionInfo.SolutionAttributes _solutionAttributes; private readonly SolutionServices _solutionServices; private readonly ImmutableDictionary _projectIdToProjectStateMap; + private readonly ImmutableHashSet _remoteSupportedLanguages; private readonly ImmutableDictionary> _filePathToDocumentIdsMap; private readonly ProjectDependencyGraph _dependencyGraph; @@ -74,6 +75,7 @@ private SolutionState( SerializableOptionSet options, IReadOnlyList analyzerReferences, ImmutableDictionary idToProjectStateMap, + ImmutableHashSet remoteSupportedLanguages, ImmutableDictionary projectIdToTrackerMap, ImmutableDictionary> filePathToDocumentIdsMap, ProjectDependencyGraph dependencyGraph, @@ -88,6 +90,7 @@ private SolutionState( Options = options; AnalyzerReferences = analyzerReferences; _projectIdToProjectStateMap = idToProjectStateMap; + _remoteSupportedLanguages = remoteSupportedLanguages; _projectIdToTrackerMap = projectIdToTrackerMap; _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; @@ -119,6 +122,7 @@ public SolutionState( options, analyzerReferences, idToProjectStateMap: ImmutableDictionary.Empty, + remoteSupportedLanguages: ImmutableHashSet.Empty, projectIdToTrackerMap: ImmutableDictionary.Empty, filePathToDocumentIdsMap: ImmutableDictionary.Create>(StringComparer.OrdinalIgnoreCase), dependencyGraph: ProjectDependencyGraph.Empty, @@ -203,6 +207,8 @@ private void CheckInvariants() // project ids must be the same: Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(ProjectIds)); Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(_dependencyGraph.ProjectIds)); + + Debug.Assert(_remoteSupportedLanguages.SetEquals(GetRemoteSupportedProjectLanguages(_projectIdToProjectStateMap))); } private SolutionState Branch( @@ -211,6 +217,7 @@ private SolutionState Branch( SerializableOptionSet? options = null, IReadOnlyList? analyzerReferences = null, ImmutableDictionary? idToProjectStateMap = null, + ImmutableHashSet? remoteSupportedProjectLanguages = null, ImmutableDictionary? projectIdToTrackerMap = null, ImmutableDictionary>? filePathToDocumentIdsMap = null, ProjectDependencyGraph? dependencyGraph = null, @@ -218,10 +225,17 @@ private SolutionState Branch( { var branchId = GetBranchId(); + if (idToProjectStateMap is not null) + { + Contract.ThrowIfNull(remoteSupportedProjectLanguages); + } + solutionAttributes ??= _solutionAttributes; projectIds ??= ProjectIds; idToProjectStateMap ??= _projectIdToProjectStateMap; - options ??= Options.WithLanguages(GetRemoteSupportedProjectLanguages(idToProjectStateMap)); + remoteSupportedProjectLanguages ??= _remoteSupportedLanguages; + Debug.Assert(remoteSupportedProjectLanguages.SetEquals(GetRemoteSupportedProjectLanguages(idToProjectStateMap))); + options ??= Options.WithLanguages(remoteSupportedProjectLanguages); analyzerReferences ??= AnalyzerReferences; projectIdToTrackerMap ??= _projectIdToTrackerMap; filePathToDocumentIdsMap ??= _filePathToDocumentIdsMap; @@ -253,6 +267,7 @@ private SolutionState Branch( options, analyzerReferences, idToProjectStateMap, + remoteSupportedProjectLanguages, projectIdToTrackerMap, filePathToDocumentIdsMap, dependencyGraph, @@ -281,6 +296,7 @@ private SolutionState CreatePrimarySolution( Options, AnalyzerReferences, _projectIdToProjectStateMap, + _remoteSupportedLanguages, _projectIdToTrackerMap, _filePathToDocumentIdsMap, _dependencyGraph, @@ -354,7 +370,7 @@ public bool ContainsAnalyzerConfigDocument([NotNullWhen(returnValue: true)] Docu private DocumentState GetRequiredDocumentState(DocumentId documentId) => GetRequiredProjectState(documentId.ProjectId).DocumentStates.GetRequiredState(documentId); - private TextDocumentState GetRequiredAdditionalDocumentState(DocumentId documentId) + private AdditionalDocumentState GetRequiredAdditionalDocumentState(DocumentId documentId) => GetRequiredProjectState(documentId.ProjectId).AdditionalDocumentStates.GetRequiredState(documentId); private AnalyzerConfigDocumentState GetRequiredAnalyzerConfigDocumentState(DocumentId documentId) @@ -461,6 +477,9 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) var newProjectIds = ProjectIds.ToImmutableArray().Add(projectId); var newStateMap = _projectIdToProjectStateMap.Add(projectId, projectState); + var newLanguages = RemoteSupportedLanguages.IsSupported(projectState.Language) + ? _remoteSupportedLanguages.Add(projectState.Language) + : _remoteSupportedLanguages; var newDependencyGraph = _dependencyGraph .WithAdditionalProject(projectId) @@ -490,6 +509,7 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); @@ -552,9 +572,9 @@ private ImmutableDictionary> CreateFilePathTo } private static IEnumerable GetDocumentStates(ProjectState projectState) - => projectState.DocumentStates.States - .Concat(projectState.AdditionalDocumentStates.States) - .Concat(projectState.AnalyzerConfigDocumentStates.States); + => projectState.DocumentStates.States.Values + .Concat(projectState.AdditionalDocumentStates.States.Values) + .Concat(projectState.AnalyzerConfigDocumentStates.States.Values); /// /// Create a new solution instance without the project specified. @@ -573,6 +593,31 @@ public SolutionState RemoveProject(ProjectId projectId) var newProjectIds = ProjectIds.ToImmutableArray().Remove(projectId); var newStateMap = _projectIdToProjectStateMap.Remove(projectId); + + // Remote supported languages only changes if the removed project is the last project of a supported language. + var newLanguages = _remoteSupportedLanguages; + if (_projectIdToProjectStateMap.TryGetValue(projectId, out var projectState) + && RemoteSupportedLanguages.IsSupported(projectState.Language)) + { + var stillSupportsLanguage = false; + foreach (var (id, state) in _projectIdToProjectStateMap) + { + if (id == projectId) + continue; + + if (state.Language == projectState.Language) + { + stillSupportsLanguage = true; + break; + } + } + + if (!stillSupportsLanguage) + { + newLanguages = newLanguages.Remove(projectState.Language); + } + } + var newDependencyGraph = _dependencyGraph.WithProjectRemoved(projectId); var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithRemovedDocuments(GetDocumentStates(_projectIdToProjectStateMap[projectId])); @@ -581,6 +626,7 @@ public SolutionState RemoveProject(ProjectId projectId) solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap.Remove(projectId), filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); @@ -759,7 +805,7 @@ public SolutionState WithProjectCompilationOptions(ProjectId projectId, Compilat return this; } - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(options)); + return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: false)); } /// @@ -784,7 +830,7 @@ public SolutionState WithProjectParseOptions(ProjectId projectId, ParseOptions o } else { - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject)); + return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject, isParseOptionChange: true)); } } @@ -930,7 +976,7 @@ public SolutionState WithProjectDocumentsOrder(ProjectId projectId, ImmutableLis return this; } - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject)); + return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject, isParseOptionChange: false)); } /// @@ -1105,7 +1151,7 @@ private SolutionState AddDocumentsToMultipleProjects( public SolutionState AddAdditionalDocuments(ImmutableArray documentInfos) { return AddDocumentsToMultipleProjects(documentInfos, - (documentInfo, project) => new TextDocumentState(documentInfo, _solutionServices), + (documentInfo, project) => new AdditionalDocumentState(documentInfo, _solutionServices), (projectState, documents) => (projectState.AddAdditionalDocuments(documents), new CompilationAndGeneratorDriverTranslationAction.AddAdditionalDocumentsAction(documents))); } @@ -1117,7 +1163,7 @@ public SolutionState AddAnalyzerConfigDocuments(ImmutableArray doc (oldProject, documents) => { var newProject = oldProject.AddAnalyzerConfigDocuments(documents); - return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject.CompilationOptions!)); + return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true)); }); } @@ -1128,7 +1174,7 @@ public SolutionState RemoveAnalyzerConfigDocuments(ImmutableArray do (oldProject, documentIds, _) => { var newProject = oldProject.RemoveAnalyzerConfigDocuments(documentIds); - return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject.CompilationOptions!)); + return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true)); }); } @@ -1425,7 +1471,7 @@ private SolutionState UpdateDocumentState(DocumentState newDocument, bool textCh newFilePathToDocumentIdsMap: newFilePathToDocumentIdsMap); } - private SolutionState UpdateAdditionalDocumentState(TextDocumentState newDocument, bool textChanged = false, bool recalculateDependentVersions = false) + private SolutionState UpdateAdditionalDocumentState(AdditionalDocumentState newDocument, bool textChanged = false, bool recalculateDependentVersions = false) { var oldProject = GetProjectState(newDocument.Id.ProjectId)!; var newProject = oldProject.UpdateAdditionalDocument(newDocument, textChanged, recalculateDependentVersions); @@ -1449,7 +1495,7 @@ private SolutionState UpdateAnalyzerConfigDocumentState(AnalyzerConfigDocumentSt Debug.Assert(oldProject != newProject); return ForkProject(newProject, - newProject.CompilationOptions != null ? new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject.CompilationOptions) : null); + newProject.CompilationOptions != null ? new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true) : null); } /// @@ -1468,6 +1514,13 @@ private SolutionState ForkProject( var projectId = newProjectState.Id; var newStateMap = _projectIdToProjectStateMap.SetItem(projectId, newProjectState); + + // Remote supported languages can only change if the project changes language. This is an unexpected edge + // case, so it's not optimized for incremental updates. + var newLanguages = !_projectIdToProjectStateMap.TryGetValue(projectId, out var projectState) || projectState.Language != newProjectState.Language + ? GetRemoteSupportedProjectLanguages(newStateMap) + : _remoteSupportedLanguages; + newDependencyGraph ??= _dependencyGraph; var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); // If we have a tracker for this project, then fork it as well (along with the @@ -1484,6 +1537,7 @@ private SolutionState ForkProject( return this.Branch( idToProjectStateMap: newStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap, dependencyGraph: newDependencyGraph, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap ?? _filePathToDocumentIdsMap); @@ -1671,10 +1725,12 @@ public SolutionState WithFrozenPartialCompilationIncludingSpecificDocument(Docum var newTracker = tracker.FreezePartialStateWithTree(this, doc, tree, cancellationToken); var newIdToProjectStateMap = _projectIdToProjectStateMap.SetItem(documentId.ProjectId, newTracker.ProjectState); + var newLanguages = _remoteSupportedLanguages; var newIdToTrackerMap = _projectIdToTrackerMap.SetItem(documentId.ProjectId, newTracker); currentPartialSolution = this.Branch( idToProjectStateMap: newIdToProjectStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newIdToTrackerMap, dependencyGraph: CreateDependencyGraph(ProjectIds, newIdToProjectStateMap)); @@ -2002,7 +2058,7 @@ internal bool ContainsTransitiveReference(ProjectId fromProjectId, ProjectId toP => _dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(fromProjectId).Contains(toProjectId); internal ImmutableHashSet GetRemoteSupportedProjectLanguages() - => GetRemoteSupportedProjectLanguages(ProjectStates); + => _remoteSupportedLanguages; private static ImmutableHashSet GetRemoteSupportedProjectLanguages(ImmutableDictionary projectStates) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs index 85bdbd828f310..72ea5f15aa772 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs @@ -36,7 +36,7 @@ public SourceGeneratedDocumentIdentity(DocumentId documentId, string hintName, s public static string GetGeneratorTypeName(ISourceGenerator generator) { - return generator.GetType().FullName!; + return generator.GetGeneratorType().FullName!; } public static string GetGeneratorAssemblyName(ISourceGenerator generator) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 371f5f47389a3..b817dd9581326 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -83,8 +83,8 @@ public TState GetRequiredState(DocumentId documentId) /// /// States ordered by . /// - public IEnumerable States - => _map.Values; + public ImmutableSortedDictionary States + => _map; /// /// Get states ordered in compilation order. diff --git a/src/Workspaces/CoreTest/Differencing/LongestCommonSubsequenceTests.cs b/src/Workspaces/CoreTest/Differencing/LongestCommonSubsequenceTests.cs index 76bd073234001..08f7203c3536e 100644 --- a/src/Workspaces/CoreTest/Differencing/LongestCommonSubsequenceTests.cs +++ b/src/Workspaces/CoreTest/Differencing/LongestCommonSubsequenceTests.cs @@ -37,6 +37,7 @@ private static void VerifyMatchingPairs(IEnumerable> actu { sb.AppendFormat("[{0},{1}]", actPair.Key, actPair.Value); } + var actualPairsStr = sb.ToString(); Assert.Equal(expectedPairsStr, actualPairsStr); } diff --git a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs index e963f875d7c52..74c052d76593d 100644 --- a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs +++ b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs @@ -170,7 +170,9 @@ internal void GetFeatureDisplayName(Type serviceInterface, ServiceDescriptor des Assert.NotNull(serviceInterface); var expectedName = descriptor32.GetFeatureDisplayName(); - Assert.NotEmpty(expectedName); + + // The service name couldn't be found. It may need to be added to RemoteWorkspacesResources.resx as FeatureName_{name} + Assert.False(string.IsNullOrEmpty(expectedName), $"Service name for '{serviceInterface.GetType()}' not available."); Assert.Equal(expectedName, descriptor64.GetFeatureDisplayName()); Assert.Equal(expectedName, descriptor64ServerGC.GetFeatureDisplayName()); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index 5e515eaf59705..a268cc0ba6e43 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -20,15 +21,25 @@ namespace Microsoft.CodeAnalysis.UnitTests [UseExportProvider] public class SolutionWithSourceGeneratorTests : TestBase { + // This is used to add on the preview language version which controls incremental generators being allowed. + // TODO: remove this method entirely and the calls once incremental generators are no longer preview + private static Project WithPreviewLanguageVersion(Project project) + { + return project.WithParseOptions(((CSharpParseOptions)project.ParseOptions!).WithLanguageVersion(LanguageVersion.Preview)); + } + [Theory] [CombinatorialData] - public async Task SourceGeneratorBasedOnAdditionalFileGeneratesSyntaxTreesOnce( + public async Task SourceGeneratorBasedOnAdditionalFileGeneratesSyntaxTrees( bool fetchCompilationBeforeAddingGenerator, bool useRecoverableTrees) { + // This test is just the sanity test to make sure generators work at all. There's not a special scenario being + // tested. + using var workspace = useRecoverableTrees ? CreateWorkspaceWithRecoverableSyntaxTreesAndWeakCompilations() : CreateWorkspace(); - var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented() { }); - var project = AddEmptyProject(workspace.CurrentSolution) + var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference); // Optionally fetch the compilation first, which validates that we handle both running the generator @@ -54,12 +65,50 @@ public async Task SourceGeneratorBasedOnAdditionalFileGeneratesSyntaxTreesOnce( Assert.Same(generatedTree, await generatedDocument.GetSyntaxTreeAsync()); } + [Fact] + public async Task IncrementalSourceGeneratorInvokedCorrectNumberOfTimes() + { + using var workspace = CreateWorkspace(); + var generator = new GenerateFileForEachAdditionalFileWithContentsCommented(); + var analyzerReference = new TestGeneratorReference(generator); + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) + .AddAnalyzerReference(analyzerReference) + .AddAdditionalDocument("Test.txt", "Hello, world!").Project + .AddAdditionalDocument("Test2.txt", "Hello, world!").Project; + + var compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + Assert.Equal(2, generator.AdditionalFilesConvertedCount); + + // Change one of the additional documents, and rerun; we should only reprocess that one change, since this + // is an incremental generator. + project = project.AdditionalDocuments.First().WithAdditionalDocumentText(SourceText.From("Changed text!")).Project; + + compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + + // We should now have converted three additional files -- the two from the original run and then the one that was changed. + // The other one should have been kept constant because that didn't change. + Assert.Equal(3, generator.AdditionalFilesConvertedCount); + + // Change one of the source documents, and rerun; we should again only reprocess that one change. + project = project.AddDocument("Source.cs", SourceText.From("")).Project; + + compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); + + // We have one extra syntax tree now, but it did not require any invocations of the incremental generator. + Assert.Equal(3, compilation.SyntaxTrees.Count()); + Assert.Equal(3, generator.AdditionalFilesConvertedCount); + } + [Fact] public async Task SourceGeneratorContentStillIncludedAfterSourceFileChange() { using var workspace = CreateWorkspace(); - var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented() { }); - var project = AddEmptyProject(workspace.CurrentSolution) + var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddDocument("Hello.cs", "// Source File").Project .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -90,8 +139,8 @@ static async Task AssertCompilationContainsOneRegularAndOneGeneratedFile(Project public async Task SourceGeneratorContentChangesAfterAdditionalFileChanges() { using var workspace = CreateWorkspace(); - var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented() { }); - var project = AddEmptyProject(workspace.CurrentSolution) + var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -119,7 +168,7 @@ public async Task PartialCompilationsIncludeGeneratedFilesAfterFullGeneration() { using var workspace = CreateWorkspace(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var project = AddEmptyProject(workspace.CurrentSolution) + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddDocument("Hello.cs", "// Source File").Project .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -139,7 +188,7 @@ public async Task DocumentIdOfGeneratedDocumentsIsStable() { using var workspace = CreateWorkspace(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var projectBeforeChange = AddEmptyProject(workspace.CurrentSolution) + var projectBeforeChange = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -178,7 +227,7 @@ public async Task DocumentIdGuidInDifferentProjectsIsDifferent() static Solution AddProjectWithReference(Solution solution, TestGeneratorReference analyzerReference) { - var project = AddEmptyProject(solution); + var project = WithPreviewLanguageVersion(AddEmptyProject(solution)); project = project.AddAnalyzerReference(analyzerReference); project = project.AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -191,7 +240,7 @@ public async Task CompilationsInCompilationReferencesIncludeGeneratedSourceFiles { using var workspace = CreateWorkspace(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var solution = AddEmptyProject(workspace.CurrentSolution) + var solution = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", "Hello, world!").Project.Solution; @@ -207,6 +256,7 @@ public async Task CompilationsInCompilationReferencesIncludeGeneratedSourceFiles var compilationWithGenerator = await solution.GetRequiredProject(projectIdWithGenerator).GetRequiredCompilationAsync(CancellationToken.None); + Assert.NotEmpty(compilationWithGenerator.SyntaxTrees); Assert.Same(compilationWithGenerator, compilationReference.Compilation); } @@ -215,7 +265,7 @@ public async Task RequestingGeneratedDocumentsTwiceGivesSameInstance() { using var workspace = CreateWorkspaceWithRecoverableSyntaxTreesAndWeakCompilations(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var project = AddEmptyProject(workspace.CurrentSolution) + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -239,7 +289,7 @@ public async Task GetDocumentWithGeneratedTreeReturnsGeneratedDocument() { using var workspace = CreateWorkspace(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var project = AddEmptyProject(workspace.CurrentSolution) + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -253,7 +303,7 @@ public async Task GetDocumentWithGeneratedTreeForInProgressReturnsGeneratedDocum { using var workspace = CreateWorkspace(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var project = AddEmptyProject(workspace.CurrentSolution) + var project = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project .AddAdditionalDocument("Test.txt", "Hello, world!").Project; @@ -427,7 +477,7 @@ public async Task OpenSourceGeneratedFileMatchesBufferContentsEvenIfGeneratedFil { using var workspace = CreateWorkspace(); var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); - var originalAdditionalFile = AddEmptyProject(workspace.CurrentSolution) + var originalAdditionalFile = WithPreviewLanguageVersion(AddEmptyProject(workspace.CurrentSolution)) .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", SourceText.From("")); diff --git a/src/Workspaces/CoreTestUtilities/GenerateFileForEachAdditionalFileWithContentsCommented.cs b/src/Workspaces/CoreTestUtilities/GenerateFileForEachAdditionalFileWithContentsCommented.cs index c102036887979..88eee34dc57f3 100644 --- a/src/Workspaces/CoreTestUtilities/GenerateFileForEachAdditionalFileWithContentsCommented.cs +++ b/src/Workspaces/CoreTestUtilities/GenerateFileForEachAdditionalFileWithContentsCommented.cs @@ -4,38 +4,45 @@ using System.IO; using System.Text; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Roslyn.Test.Utilities { - internal sealed class GenerateFileForEachAdditionalFileWithContentsCommented : ISourceGenerator + internal sealed class GenerateFileForEachAdditionalFileWithContentsCommented : IIncrementalGenerator { - public void Execute(GeneratorExecutionContext context) - { - foreach (var file in context.AdditionalFiles) - { - AddSourceForAdditionalFile(context, file); - } - } + /// + /// This should only be updated with Interlocked APIs. + /// + private int _additionalFilesConvertedCount; - public void Initialize(GeneratorInitializationContext context) + /// + /// The number of additional files we converted to a source file. This can be used to assert incrementality. + /// + public int AdditionalFilesConvertedCount => _additionalFilesConvertedCount; + + public void Initialize(IncrementalGeneratorInitializationContext context) { - // TODO: context.RegisterForAdditionalFileChanges(UpdateContext); + context.RegisterSourceOutput(context.AdditionalTextsProvider, (context, additionalText) => + context.AddSource( + GetGeneratedFileName(additionalText.Path), + GenerateSourceForAdditionalFile(additionalText, context.CancellationToken))); } - private static void AddSourceForAdditionalFile(GeneratorExecutionContext context, AdditionalText file) + private SourceText GenerateSourceForAdditionalFile(AdditionalText file, CancellationToken cancellationToken) { + Interlocked.Increment(ref _additionalFilesConvertedCount); + // We're going to "comment" out the contents of the file when generating this - var sourceText = file.GetText(context.CancellationToken); + var sourceText = file.GetText(cancellationToken); Contract.ThrowIfNull(sourceText, "Failed to fetch the text of an additional file."); var changes = sourceText.Lines.SelectAsArray(l => new TextChange(new TextSpan(l.Start, length: 0), "// ")); var generatedText = sourceText.WithChanges(changes); - // TODO: remove the generatedText.ToString() when I don't have to specify the encoding - context.AddSource(GetGeneratedFileName(file.Path), SourceText.From(generatedText.ToString(), encoding: Encoding.UTF8)); + return SourceText.From(generatedText.ToString(), encoding: Encoding.UTF8); } private static string GetGeneratedFileName(string path) => $"{Path.GetFileNameWithoutExtension(path)}.generated"; diff --git a/src/Workspaces/CoreTestUtilities/ObjectExtensions.cs b/src/Workspaces/CoreTestUtilities/ObjectExtensions.cs index b3c8d65a8b58c..db2efe35a5ea0 100644 --- a/src/Workspaces/CoreTestUtilities/ObjectExtensions.cs +++ b/src/Workspaces/CoreTestUtilities/ObjectExtensions.cs @@ -24,6 +24,7 @@ public static object GetPropertyValue(this object instance, string propertyName) { throw new ArgumentException("Property " + propertyName + " was not found on type " + type.ToString()); } + var result = propertyInfo.GetValue(instance, null); return result; } @@ -39,6 +40,7 @@ public static object GetFieldValue(this object instance, string fieldName) { break; } + type = type.BaseType; } @@ -46,6 +48,7 @@ public static object GetFieldValue(this object instance, string fieldName) { throw new FieldAccessException("Field " + fieldName + " was not found on type " + type.ToString()); } + var result = fieldInfo.GetValue(instance); return result; // you can place a breakpoint here (for debugging purposes) } diff --git a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs index deafef00f9a6b..7ba5994514480 100644 --- a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs +++ b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs @@ -49,6 +49,10 @@ public void Add(PerLanguageOption2> option, T value) public void Add(PerLanguageOption2> option, T value, NotificationOption2 notification) => _options.Add(new OptionKey2(option, _languageName), new CodeStyleOption2(value, notification)); + // 📝 This can be removed if/when collection initializers support AddRange. + public void Add(OptionsCollection options) + => AddRange(options); + public void AddRange(OptionsCollection options) { foreach (var (key, value) in options) diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index bfba9ace063f2..0ecc2e85f7c20 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -270,6 +270,7 @@ public InProcRemoteServices(HostWorkspaceServices workspaceServices, TraceListen RegisterRemoteBrokeredService(new RemoteGlobalNotificationDeliveryService.Factory()); RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); RegisterRemoteBrokeredService(new RemoteEditAndContinueService.Factory()); + RegisterRemoteBrokeredService(new RemoteValueTrackingService.Factory()); RegisterRemoteBrokeredService(new RemoteInheritanceMarginService.Factory()); } diff --git a/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs b/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs index 301521aa7175d..fff413c2ac07c 100644 --- a/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs +++ b/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs @@ -33,6 +33,11 @@ public TestGeneratorReference(ISourceGenerator generator) _checksum = Checksum.From(checksumArray); } + public TestGeneratorReference(IIncrementalGenerator generator) + : this(generator.AsSourceGenerator()) + { + } + public override string? FullPath => null; public override object Id => this; public Guid Guid { get; } diff --git a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj index 1e6a39c0aa780..7c050ecb2ab8c 100644 --- a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj +++ b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj @@ -43,7 +43,9 @@ + + diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index 1b2e8b945489a..1e8ee92ac65c7 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -201,4 +201,7 @@ Inheritance margin + + Value Tracking + \ No newline at end of file diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index b4231bbce3995..e2150408be908 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -26,6 +26,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SymbolSearch; using Microsoft.CodeAnalysis.TodoComments; +using Microsoft.CodeAnalysis.ValueTracking; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote @@ -70,6 +71,7 @@ internal sealed class ServiceDescriptors (typeof(IRemoteGlobalNotificationDeliveryService), null), (typeof(IRemoteCodeLensReferencesService), null), (typeof(IRemoteEditAndContinueService), typeof(IRemoteEditAndContinueService.ICallback)), + (typeof(IRemoteValueTrackingService), null), (typeof(IRemoteInheritanceMarginService), null), }); diff --git a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs b/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs index 0878c25167aac..447b49f1803e2 100644 --- a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs +++ b/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; @@ -34,7 +35,7 @@ internal sealed class SolutionChecksumUpdater : GlobalOperationAwareIdleProcesso public SolutionChecksumUpdater(Workspace workspace, IAsynchronousOperationListenerProvider listenerProvider, CancellationToken shutdownToken) : base(listenerProvider.GetListener(FeatureAttribute.SolutionChecksumUpdater), workspace.Services.GetService(), - workspace.Options.GetOption(RemoteHostOptions.SolutionChecksumMonitorBackOffTimeSpanInMS), shutdownToken) + TimeSpan.FromMilliseconds(workspace.Options.GetOption(RemoteHostOptions.SolutionChecksumMonitorBackOffTimeSpanInMS)), shutdownToken) { _workspace = workspace; _textChangeQueue = new TaskQueue(Listener, TaskScheduler.Default); diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index bd5a5c8704996..cc1dd9eca9ed3 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -127,6 +127,11 @@ TODO zjišťování komentářů + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' Kvůli přechodné chybě není funkce {0} momentálně k dispozici. Zkuste to prosím znovu později: {1} diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index fc7996507456d..cd7b97d9d77a5 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -127,6 +127,11 @@ TODO Kommentarermittlung + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' Das Feature "{0}" ist aufgrund eines zeitweiligen Fehlers momentan nicht verfügbar. Versuchen Sie es später noch mal: "{1}" diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 99b8c4416bd8a..2c50d40ad0c6a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -127,6 +127,11 @@ Detección de comentarios TODO + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' La característica "{0}" no está disponible debido a un error intermitente; vuelva a intentarlo más tarde: "{1}" diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 115fbd2523ec4..af8b3ca60f428 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -127,6 +127,11 @@ Détection de commentaires TODO + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' La fonctionnalité '{0}' est non disponible en raison d'une erreur intermittente. Réessayez plus tard : '{1}' diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index 675038078c2f8..cc4a2b5128487 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -127,6 +127,11 @@ Individuazione dei commenti TODO + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' La funzionalità '{0}' non è attualmente disponibile a causa di un errore intermittente. Riprovare più tardi: '{1}' diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index 506710741c89b..412439c5fe36f 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -127,6 +127,11 @@ TODO コメント検出 + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' 間欠的なエラーのため、機能 '{0}' は現在使用できません。後でもう一度お試しください: '{1}' diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index ef559a20f17ce..f2d7b14727ca6 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -127,6 +127,11 @@ TODO 주석 검색 + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' 일시적 오류로 인해 기능 '{0}'을(를) 현재 사용할 수 없습니다. 나중에 다시 시도하세요. '{1}' diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index 9c6df3ce48c97..cdd5b37ce6dcc 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -127,6 +127,11 @@ Odnajdywanie komentarzy TODO + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' Funkcja „{0}” jest obecnie niedostępna z powodu sporadycznego błędu, spróbuj ponownie później: „{1}” diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index 3632718305b99..9ae3be71fe172 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -127,6 +127,11 @@ Descoberta de comentários TODO + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' O recurso '{0}' não está disponível no momento devido a um erro intermitente. Faça uma nova tentativa mais tarde: '{1}' diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index 14a16e0069e9b..08a0b4dc27be0 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -127,6 +127,11 @@ Обнаружение комментариев TODO + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' Функция "{0}" сейчас недоступна из-за временной ошибки. Повторите попытку позже: "{1}". diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index 67ceac7e373aa..12ea5ffefb2cd 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -127,6 +127,11 @@ TODO açıklamalarını bulma + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' '{0}' özelliği, aralıklı bir hata nedeniyle şu anda kullanılamıyor. Lütfen daha sonra yeniden deneyin: '{1}' diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index a5d85966661c9..2c0890a61bab4 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -127,6 +127,11 @@ TODO 注释发现 + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' 功能“{0}”当前不可用,因为出现间歇性错误,请稍后重试:“{1}” diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index 3128d92209ca3..c52ca11f27c71 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -127,6 +127,11 @@ TODO 註解探索 + + Value Tracking + Value Tracking + + Feature '{0}' is currently unavailable due to an intermittent error, please try again later: '{1}' 因為發生間歇性錯誤,所以 '{0}' 功能目前無法使用,請稍後再試: '{1}' diff --git a/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs b/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs index c53e0538403c1..0d58c936114b2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Remote [Shared] internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory { - private const int ImplicitCacheTimeoutInMS = 10000; + private static readonly TimeSpan s_implicitCacheTimeout = TimeSpan.FromMilliseconds(10000); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -22,6 +22,6 @@ public ProjectCacheHostServiceFactory() } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + => new ProjectCacheService(workspaceServices.Workspace, s_implicitCacheTimeout); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index b1e4b22cebb2a..4c9794f4a63fc 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -242,7 +242,7 @@ await _assetProvider.GetAssetAsync( { project = await UpdateDocumentsAsync( project, - project.State.DocumentStates.States, + project.State.DocumentStates.States.Values, oldProjectChecksums.Documents, newProjectChecksums.Documents, (solution, documents) => solution.AddDocuments(documents), @@ -254,7 +254,7 @@ await _assetProvider.GetAssetAsync( { project = await UpdateDocumentsAsync( project, - project.State.AdditionalDocumentStates.States, + project.State.AdditionalDocumentStates.States.Values, oldProjectChecksums.AdditionalDocuments, newProjectChecksums.AdditionalDocuments, (solution, documents) => solution.AddAdditionalDocuments(documents), @@ -266,7 +266,7 @@ await _assetProvider.GetAssetAsync( { project = await UpdateDocumentsAsync( project, - project.State.AnalyzerConfigDocumentStates.States, + project.State.AnalyzerConfigDocumentStates.States.Values, oldProjectChecksums.AnalyzerConfigDocuments, newProjectChecksums.AnalyzerConfigDocuments, (solution, documents) => solution.AddAnalyzerConfigDocuments(documents), diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj index 5ad1f662f2edb..3be317d15396b 100644 --- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj @@ -48,7 +48,9 @@ + + diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceQueue.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceQueue.cs index 6693cd16b0753..92b5a15249b5f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceQueue.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceQueue.cs @@ -125,8 +125,8 @@ private class Snapshot /// private readonly Dictionary _performanceMap; - public Snapshot(IEnumerable<(string analyzerId, TimeSpan timeSpan)> snapshot, int unitCount) : - this(Convert(snapshot), unitCount) + public Snapshot(IEnumerable<(string analyzerId, TimeSpan timeSpan)> snapshot, int unitCount) + : this(Convert(snapshot), unitCount) { } diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs index d6cb2cde53457..2cfbc915461e0 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs @@ -44,8 +44,8 @@ internal class PerformanceTrackerService : IPerformanceTrackerService [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public PerformanceTrackerService() : - this(DefaultMinLOFValue, DefaultAverageThreshold, DefaultStddevThreshold) + public PerformanceTrackerService() + : this(DefaultMinLOFValue, DefaultAverageThreshold, DefaultStddevThreshold) { } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs index deaa218aec9c3..e6e77c694d198 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs @@ -43,7 +43,7 @@ public PerformanceReporter( : base( AsynchronousOperationListenerProvider.NullListener, globalOperationNotificationService, - (int)reportingInterval.TotalMilliseconds, + reportingInterval, shutdownToken) { _event = new SemaphoreSlim(initialCount: 0); diff --git a/src/Workspaces/Remote/ServiceHub/Services/ValueTracking/RemoteValueTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ValueTracking/RemoteValueTrackingService.cs new file mode 100644 index 0000000000000..7f3f9e86ac4a6 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/ValueTracking/RemoteValueTrackingService.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.ValueTracking; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal sealed class RemoteValueTrackingService : BrokeredServiceBase, IRemoteValueTrackingService + { + internal sealed class Factory : FactoryBase + { + protected override IRemoteValueTrackingService CreateService(in ServiceConstructionArguments arguments) + => new RemoteValueTrackingService(arguments); + } + + public RemoteValueTrackingService(ServiceConstructionArguments arguments) + : base(arguments) + { + } + + public ValueTask> TrackValueSourceAsync(PinnedSolutionInfo solutionInfo, TextSpan selection, DocumentId documentId, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => + { + var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); + if (solution is null) + { + throw new InvalidOperationException(); + } + + var document = solution.GetRequiredDocument(documentId); + + var progress = new ValueTrackingProgressCollector(); + await ValueTracker.TrackValueSourceAsync(selection, document, progress, cancellationToken).ConfigureAwait(false); + + var items = progress.GetItems(); + return items.SelectAsArray(item => SerializableValueTrackedItem.Dehydrate(solution, item, cancellationToken)); + }, cancellationToken); + } + + public ValueTask> TrackValueSourceAsync(PinnedSolutionInfo solutionInfo, SerializableValueTrackedItem previousTrackedItem, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => + { + var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); + if (solution is null) + { + throw new InvalidOperationException(); + } + + var previousItem = await previousTrackedItem.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (previousItem is null) + { + return ImmutableArray.Empty; + } + + var progress = new ValueTrackingProgressCollector(); + await ValueTracker.TrackValueSourceAsync(solution, previousItem, progress, cancellationToken).ConfigureAwait(false); + + var items = progress.GetItems(); + return items.SelectAsArray(item => SerializableValueTrackedItem.Dehydrate(solution, item, cancellationToken)); + }, cancellationToken); + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs index 2840c20a52f23..58b1de4eecbec 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs @@ -268,6 +268,12 @@ private static Option2> CreateUsingDirectiv "csharp_style_implicit_object_creation_when_type_is_apparent", "TextEditor.CSharp.Specific.ImplicitObjectCreationWhenTypeIsApparent"); + internal static readonly Option2> PreferNullCheckOverTypeCheck = CreateOption( + CSharpCodeStyleOptionGroups.ExpressionLevelPreferences, nameof(PreferNullCheckOverTypeCheck), + defaultValue: s_trueWithSuggestionEnforcement, + "csharp_style_prefer_null_check_over_type_check", + $"TextEditor.CSharp.Specific.{nameof(PreferNullCheckOverTypeCheck)}"); + public static Option2> AllowEmbeddedStatementsOnSameLine { get; } = CreateOption( CSharpCodeStyleOptionGroups.NewLinePreferences, nameof(AllowEmbeddedStatementsOnSameLine), defaultValue: CodeStyleOptions2.TrueWithSilentEnforcement, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs index 4921a460d7edb..bf387e4683768 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs @@ -98,6 +98,7 @@ private static VirtualCharSequence TryConvertStringToVirtualChars( Debug.Fail("This should not be reachable as long as the compiler added no diagnostics."); return default; } + if (endDelimiter.Length > 0 && !tokenText.EndsWith(endDelimiter)) { Debug.Fail("This should not be reachable as long as the compiler added no diagnostics."); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs index 7c1df6fa80aa5..65ca1c846c9ce 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs @@ -10,7 +10,7 @@ public static bool IsCSharp9OrAbove(this LanguageVersion languageVersion) => languageVersion >= LanguageVersion.CSharp9; public static bool IsCSharp10OrAbove(this LanguageVersion languageVersion) - => languageVersion >= LanguageVersion.Preview; + => languageVersion >= LanguageVersion.CSharp10; public static bool HasConstantInterpolatedStrings(this LanguageVersion languageVersion) => languageVersion.IsCSharp10OrAbove(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index b13fd14d2f076..853461ea064cf 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -275,7 +275,7 @@ node is UsingStatementSyntax || SyntaxKind.ParenthesizedLambdaExpression => ((ParenthesizedLambdaExpressionSyntax)declaration).ParameterList, SyntaxKind.LocalFunctionStatement => ((LocalFunctionStatementSyntax)declaration).ParameterList, SyntaxKind.AnonymousMethodExpression => ((AnonymousMethodExpressionSyntax)declaration).ParameterList, - SyntaxKind.RecordDeclaration => ((RecordDeclarationSyntax)declaration).ParameterList, + SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration => ((RecordDeclarationSyntax)declaration).ParameterList, _ => null, }; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.Parsers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.Parsers.cs index fa3f0322fcff4..e35b6c78bc1fd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.Parsers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.Parsers.cs @@ -92,10 +92,12 @@ internal static bool DetermineIfNewLineOptionIsSet(string value, NewLineOption o { return option; } + if (s_legacyNewLineOptionsEditorConfigMap.TryGetValue(value, out var legacyOption)) { return legacyOption; } + return null; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/FormattingHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/FormattingHelpers.cs index 99a45e3ec9dbe..be7469abc0aee 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/FormattingHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/FormattingHelpers.cs @@ -109,6 +109,9 @@ public static bool IsColonInTypeBaseList(this SyntaxToken token) public static bool IsCommaInArgumentOrParameterList(this SyntaxToken token) => token.Kind() == SyntaxKind.CommaToken && (token.Parent.IsAnyArgumentList() || token.Parent.IsKind(SyntaxKind.ParameterList) || token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList)); + public static bool IsOpenParenInParameterListOfParenthesizedLambdaExpression(this SyntaxToken token) + => token.Kind() == SyntaxKind.OpenParenToken && token.Parent.IsKind(SyntaxKind.ParameterList) && token.Parent.Parent.IsKind(SyntaxKind.ParenthesizedLambdaExpression); + public static bool IsLambdaBodyBlock(this SyntaxNode node) { if (node.Kind() != SyntaxKind.Block) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs index 8b74c5262537e..14f0f9052e633 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting.Rules; @@ -41,7 +42,7 @@ private static bool IsControlBlock(SyntaxNode node) { RoslynDebug.Assert(node != null); - if (node.Kind() == SyntaxKind.SwitchStatement) + if (node.IsKind(SyntaxKind.SwitchStatement)) { return true; } @@ -109,7 +110,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { in the type declaration context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && (currentToken.Parent is BaseTypeDeclarationSyntax || currentToken.Parent is NamespaceDeclarationSyntax)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent is BaseTypeDeclarationSyntax or NamespaceDeclarationSyntax) { if (!_options.NewLinesForBracesInTypes) { @@ -128,9 +129,7 @@ private static bool IsControlBlock(SyntaxNode node) // new { - Object Initialization, or with { - Record with initializer, or is { - property pattern clauses if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && - (currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) || - currentToken.Parent.IsKind(SyntaxKind.WithInitializerExpression) || - currentToken.Parent.IsKind(SyntaxKind.PropertyPatternClause))) + currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression, SyntaxKind.WithInitializerExpression, SyntaxKind.PropertyPatternClause)) { if (!_options.NewLinesForBracesInObjectCollectionArrayInitializers) { @@ -141,7 +140,7 @@ private static bool IsControlBlock(SyntaxNode node) var currentTokenParentParent = currentToken.Parent.Parent; // * { - in the member declaration context - if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent != null && currentTokenParentParent is MemberDeclarationSyntax) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is MemberDeclarationSyntax) { var option = currentTokenParentParent is BasePropertyDeclarationSyntax ? _options.NewLinesForBracesInProperties @@ -153,7 +152,7 @@ private static bool IsControlBlock(SyntaxNode node) } } - if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent != null && currentTokenParentParent is AccessorDeclarationSyntax) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is AccessorDeclarationSyntax) { if (!_options.NewLinesForBracesInAccessors) { @@ -162,7 +161,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the anonymous Method context - if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent != null && currentTokenParentParent.IsKind(SyntaxKind.AnonymousMethodExpression)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.AnonymousMethodExpression)) { if (!_options.NewLinesForBracesInAnonymousMethods) { @@ -171,7 +170,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the local function context - if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent != null && currentTokenParentParent.IsKind(SyntaxKind.LocalFunctionStatement)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.LocalFunctionStatement)) { if (!_options.NewLinesForBracesInMethods) { @@ -180,8 +179,8 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the Lambda context - if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent != null && - (currentTokenParentParent.IsKind(SyntaxKind.SimpleLambdaExpression) || currentTokenParentParent.IsKind(SyntaxKind.ParenthesizedLambdaExpression))) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && + currentTokenParentParent.IsKind(SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression)) { if (!_options.NewLinesForBracesInLambdaExpressionBody) { @@ -199,7 +198,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the control statement context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && IsControlBlock(currentToken.Parent)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && IsControlBlock(currentToken.Parent)) { if (!_options.NewLinesForBracesInControlBlocks) { @@ -219,7 +218,7 @@ private static bool IsControlBlock(SyntaxNode node) // else condition is actually handled in the GetAdjustSpacesOperation() // For Object Initialization Expression - if (previousToken.Kind() == SyntaxKind.CommaToken && previousToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression)) + if (previousToken.IsKind(SyntaxKind.CommaToken) && previousToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression)) { if (_options.NewLineForMembersInObjectInit) { @@ -233,7 +232,7 @@ private static bool IsControlBlock(SyntaxNode node) } // For Anonymous Object Creation Expression - if (previousToken.Kind() == SyntaxKind.CommaToken && previousToken.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) + if (previousToken.IsKind(SyntaxKind.CommaToken) && previousToken.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) { if (_options.NewLineForMembersInAnonymousTypes) { @@ -261,7 +260,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * catch in the try catch context - if (currentToken.Kind() == SyntaxKind.CatchKeyword) + if (currentToken.IsKind(SyntaxKind.CatchKeyword)) { if (_options.NewLineForCatch) { @@ -274,7 +273,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * Finally - if (currentToken.Kind() == SyntaxKind.FinallyKeyword) + if (currentToken.IsKind(SyntaxKind.FinallyKeyword)) { if (_options.NewLineForFinally) { @@ -287,7 +286,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the type declaration context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && (currentToken.Parent is BaseTypeDeclarationSyntax || currentToken.Parent is NamespaceDeclarationSyntax)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent is BaseTypeDeclarationSyntax or NamespaceDeclarationSyntax) { if (_options.NewLinesForBracesInTypes) { @@ -300,7 +299,7 @@ private static bool IsControlBlock(SyntaxNode node) } // new { - Anonymous object creation - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && currentToken.Parent.Kind() == SyntaxKind.AnonymousObjectCreationExpression) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) { if (_options.NewLinesForBracesInAnonymousTypes) { @@ -317,10 +316,7 @@ private static bool IsControlBlock(SyntaxNode node) // with { - Record with initializer // is { - property pattern clauses if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && - (currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) || - currentToken.Parent.IsKind(SyntaxKind.CollectionInitializerExpression) || - currentToken.Parent.IsKind(SyntaxKind.WithInitializerExpression) || - currentToken.Parent.IsKind(SyntaxKind.PropertyPatternClause))) + currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.WithInitializerExpression, SyntaxKind.PropertyPatternClause)) { if (_options.NewLinesForBracesInObjectCollectionArrayInitializers) { @@ -337,8 +333,7 @@ private static bool IsControlBlock(SyntaxNode node) // new[] { // { - Implicit Array if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && - (currentToken.Parent.Kind() == SyntaxKind.ArrayInitializerExpression || - currentToken.Parent.Kind() == SyntaxKind.ImplicitArrayCreationExpression)) + currentToken.Parent.IsKind(SyntaxKind.ArrayInitializerExpression, SyntaxKind.ImplicitArrayCreationExpression)) { return null; } @@ -346,7 +341,7 @@ private static bool IsControlBlock(SyntaxNode node) var currentTokenParentParent = currentToken.Parent.Parent; // * { - in the member declaration context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && currentTokenParentParent != null && currentTokenParentParent is MemberDeclarationSyntax) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is MemberDeclarationSyntax) { var option = currentTokenParentParent is BasePropertyDeclarationSyntax ? _options.NewLinesForBracesInProperties @@ -363,7 +358,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the property accessor context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && currentTokenParentParent != null && currentTokenParentParent is AccessorDeclarationSyntax) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is AccessorDeclarationSyntax) { if (_options.NewLinesForBracesInAccessors) { @@ -376,7 +371,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the anonymous Method context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && currentTokenParentParent != null && currentTokenParentParent.Kind() == SyntaxKind.AnonymousMethodExpression) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.AnonymousMethodExpression)) { if (_options.NewLinesForBracesInAnonymousMethods) { @@ -389,7 +384,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the local function context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && currentTokenParentParent != null && currentTokenParentParent.Kind() == SyntaxKind.LocalFunctionStatement) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.LocalFunctionStatement)) { if (_options.NewLinesForBracesInMethods) { @@ -402,8 +397,8 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the simple Lambda context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && currentTokenParentParent != null && - (currentTokenParentParent.Kind() == SyntaxKind.SimpleLambdaExpression || currentTokenParentParent.Kind() == SyntaxKind.ParenthesizedLambdaExpression)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && + currentTokenParentParent.IsKind(SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression)) { if (_options.NewLinesForBracesInLambdaExpressionBody) { @@ -429,7 +424,7 @@ private static bool IsControlBlock(SyntaxNode node) } // * { - in the control statement context - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && IsControlBlock(currentToken.Parent)) + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && IsControlBlock(currentToken.Parent)) { if (_options.NewLinesForBracesInControlBlocks) { @@ -444,7 +439,7 @@ private static bool IsControlBlock(SyntaxNode node) // Wrapping - Leave statements on same line (false): // Insert a newline between the previous statement and this one. // ; * - if (previousToken.Kind() == SyntaxKind.SemicolonToken + if (previousToken.IsKind(SyntaxKind.SemicolonToken) && (previousToken.Parent is StatementSyntax && !previousToken.Parent.IsKind(SyntaxKind.ForStatement)) && !_options.WrappingKeepStatementsOnSingleLine) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs index 374f9588901bf..c19cdf32fb0b6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs @@ -52,13 +52,28 @@ public override AbstractFormattingRule WithOptions(AnalyzerConfigOptions options // For Method Declaration if (currentToken.IsOpenParenInParameterList() && previousKind == SyntaxKind.IdentifierToken) { + // Parenthesized lambda with explicit return type. + if (currentToken.IsOpenParenInParameterListOfParenthesizedLambdaExpression()) + { + return CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpacesIfOnSingleLine); + } + return AdjustSpacesOperationZeroOrOne(_options.SpacingAfterMethodDeclarationName); } // For Generic Method Declaration - if (currentToken.IsOpenParenInParameterList() && previousKind == SyntaxKind.GreaterThanToken && previousParentKind == SyntaxKind.TypeParameterList) + if (currentToken.IsOpenParenInParameterList() && previousKind == SyntaxKind.GreaterThanToken) { - return AdjustSpacesOperationZeroOrOne(_options.SpacingAfterMethodDeclarationName); + // Parenthesized lambda with explicit generic return type. + if (currentToken.IsOpenParenInParameterListOfParenthesizedLambdaExpression() && previousParentKind == SyntaxKind.TypeArgumentList) + { + return CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpacesIfOnSingleLine); + } + + if (previousParentKind == SyntaxKind.TypeParameterList) + { + return AdjustSpacesOperationZeroOrOne(_options.SpacingAfterMethodDeclarationName); + } } // Case: public static implicit operator string(Program p) { return null; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 11e1bd60da9c8..49c4a03e97194 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -880,6 +880,7 @@ public string GetDisplayName(SyntaxNode? node, DisplayNameOptions options, strin { name = "~" + name; } + if ((options & DisplayNameOptions.IncludeTypeParameters) != 0) { var pooled = PooledStringBuilder.GetInstance(); @@ -907,6 +908,7 @@ public string GetDisplayName(SyntaxNode? node, DisplayNameOptions options, strin } } } + Debug.Assert(name != null, "Unexpected node type " + node.Kind()); return name; } @@ -922,6 +924,7 @@ private static void AppendTypeParameterList(StringBuilder builder, TypeParameter builder.Append(", "); builder.Append(typeParameterList.Parameters[i].Identifier.ValueText); } + builder.Append('>'); } } @@ -2042,6 +2045,7 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) return DeclarationKind.Variable; } } + break; } @@ -2051,6 +2055,7 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) { return DeclarationKind.Attribute; } + break; case SyntaxKind.Attribute: @@ -2058,6 +2063,7 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) { return DeclarationKind.Attribute; } + break; case SyntaxKind.GetAccessorDeclaration: diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs index 0e527a0c4f36f..f4ff1fa562605 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs @@ -914,6 +914,7 @@ private static ulong FindSurprisingSignExtensionBits(IOperation? operation, bool case 2: return unchecked((ulong)(ushort)recursive); case 4: return unchecked((ulong)(uint)recursive); } + Debug.Assert(false, "How did we get here?"); return recursive; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index faf79cc6b0e29..ac9929602d103 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -39,6 +39,7 @@ + @@ -453,7 +454,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs index 5e0c583c26b07..1eaaf5a0a9d74 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs @@ -204,7 +204,8 @@ static ImmutableArray GetImplicitlyImplementableMembers(INamedTypeSymbo { return type.GetMembers().WhereAsArray(m => m.DeclaredAccessibility == Accessibility.Public && m.Kind != SymbolKind.NamedType && IsImplementable(m) && - !IsPropertyWithNonPublicImplementableAccessor(m)); + !IsPropertyWithNonPublicImplementableAccessor(m) && + IsImplicitlyImplementable(m, within)); } return type.GetMembers(); @@ -226,6 +227,22 @@ static bool IsNonPublicImplementableAccessor(IMethodSymbol? accessor) { return accessor != null && IsImplementable(accessor) && accessor.DeclaredAccessibility != Accessibility.Public; } + + static bool IsImplicitlyImplementable(ISymbol member, ISymbol within) + { + if (member is IMethodSymbol { IsStatic: true, IsAbstract: true, MethodKind: MethodKind.UserDefinedOperator } method) + { + // For example, the following is not implementable implicitly. + // interface I { static abstract int operator -(I x); } + // But the following is implementable: + // interface I where T : I { static abstract int operator -(T x); } + + // See https://github.com/dotnet/csharplang/blob/main/spec/classes.md#unary-operators. + return method.Parameters.Any(p => p.Type.Equals(within, SymbolEqualityComparer.Default)); + } + + return true; + } } private static bool IsImplementable(ISymbol m) @@ -396,7 +413,7 @@ private static ImmutableArray GetUnimplementedMembers( { var q = from m in interfaceMemberGetter(interfaceType, classOrStructType) where m.Kind != SymbolKind.NamedType - where m.Kind != SymbolKind.Method || ((IMethodSymbol)m).MethodKind == MethodKind.Ordinary + where m.Kind != SymbolKind.Method || ((IMethodSymbol)m).MethodKind is MethodKind.Ordinary or MethodKind.UserDefinedOperator where m.Kind != SymbolKind.Property || ((IPropertySymbol)m).IsIndexer || ((IPropertySymbol)m).CanBeReferencedByName where m.Kind != SymbolKind.Event || ((IEventSymbol)m).CanBeReferencedByName where !isImplemented(classOrStructType, m, isValidImplementation, cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs index db8f0b67019e5..74d4c40ee1fad 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs @@ -137,7 +137,9 @@ public static bool IsImplementableMember([NotNullWhen(returnValue: true)] this I var methodSymbol = (IMethodSymbol)symbol; if (methodSymbol.MethodKind == MethodKind.Ordinary || methodSymbol.MethodKind == MethodKind.PropertyGet || - methodSymbol.MethodKind == MethodKind.PropertySet) + methodSymbol.MethodKind == MethodKind.PropertySet || + methodSymbol.MethodKind == MethodKind.UserDefinedOperator || + methodSymbol.MethodKind == MethodKind.Conversion) { return true; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 138d11391b46d..769e4ae49edb8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -504,7 +504,8 @@ internal enum FunctionId CommandHandler_Paste_ImportsOnPaste = 470, - FindDocumentInWorkspace = 480, + // Superseded by LSP_FindDocumentInWorkspace + // obsolete: FindDocumentInWorkspace = 480, RegisterWorkspace = 481, LSP_RequestCounter = 482, @@ -519,5 +520,12 @@ internal enum FunctionId InheritanceMargin_NavigateToTarget = 488, VS_ErrorReportingService_ShowGlobalErrorInfo = 489, + + ValueTracking_Command = 490, + ValueTracking_TrackValueSource = 491, + + InheritanceMargin_GetInheritanceMemberItems = 492, + + LSP_FindDocumentInWorkspace = 493, } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs index d9c423c7bf8ca..ab10db509a070 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs @@ -198,8 +198,8 @@ private static int GetNextUniqueBlockId() /// /// simplest way to log a start and end pair /// - public static IDisposable LogBlock(FunctionId functionId, CancellationToken token) - => LogBlock(functionId, string.Empty, token); + public static IDisposable LogBlock(FunctionId functionId, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + => LogBlock(functionId, string.Empty, token, logLevel); /// /// simplest way to log a start and end pair with a simple context message which should be very cheap to create diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs index 87d43d2514f41..40322e5db8e22 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs @@ -440,7 +440,7 @@ public static bool IsLockStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(t public static bool IsReturnStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node) => node?.RawKind == syntaxFacts.SyntaxKinds.ReturnStatement; - public static bool IsUsingStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node) + public static bool IsUsingStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) => node?.RawKind == syntaxFacts.SyntaxKinds.UsingStatement; #endregion diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs index c03064717b8a1..fdae49f65a1f9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs @@ -309,23 +309,7 @@ public static Task ContinueWithAfterDelayFromAsync( this Task task, Func continuationFunction, CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWithFromAsync( - _ => continuationFunction(t), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - - public static Task ContinueWithAfterDelayFromAsync( - this Task task, - Func continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, + TimeSpan delay, IExpeditableDelaySource delaySource, TaskContinuationOptions taskContinuationOptions, TaskScheduler scheduler) @@ -333,7 +317,7 @@ public static Task ContinueWithAfterDelayFromAsync( Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); return task.SafeContinueWith(t => - delaySource.Delay(TimeSpan.FromMilliseconds(millisecondsDelay), cancellationToken).SafeContinueWithFromAsync( + delaySource.Delay(delay, cancellationToken).SafeContinueWithFromAsync( _ => continuationFunction(t), cancellationToken, TaskContinuationOptions.None, scheduler), cancellationToken, taskContinuationOptions, scheduler).Unwrap(); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/DirectiveSyntaxExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/DirectiveSyntaxExtensions.vb index 64c1818e94101..24c5b30668257 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/DirectiveSyntaxExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/DirectiveSyntaxExtensions.vb @@ -126,6 +126,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions If Not GetDirectiveInfo(directive, cancellationToken).ConditionalMap.TryGetValue(directive, result) Then Return SpecializedCollections.EmptyReadOnlyList(Of DirectiveTriviaSyntax)() End If + Return result End Function End Module diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SemanticModelExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SemanticModelExtensions.vb index fb71497a16f06..334e73bb9a0dd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SemanticModelExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SemanticModelExtensions.vb @@ -21,6 +21,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return results.OfType(Of ITypeSymbol)().ToList() End If End If + Return SpecializedCollections.EmptyList(Of ITypeSymbol)() End Function @@ -32,6 +33,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions If expression IsNot Nothing Then Return semanticModel.LookupName(expression, namespacesAndTypesOnly, cancellationToken) End If + Return SpecializedCollections.EmptyList(Of ISymbol)() End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb index c04d8c8d15650..8ed02ef44b5dc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb @@ -221,6 +221,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions SyntaxKind.SingleLineSubLambdaExpression Return True End Select + Return False End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxTreeExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxTreeExtensions.vb index 11fd4c0a9c8d3..1cf4e64ea54e9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxTreeExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxTreeExtensions.vb @@ -91,6 +91,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions If TypeOf node Is SingleLineLambdaExpressionSyntax Then Return True node = node.Parent End While + Return False End Function @@ -134,6 +135,7 @@ recurse: Return trivia End If Next + For Each trivia In child.GetLeadingTrivia.Reverse If (trivia.SpanStart < position) AndAlso (position <= child.FullSpan.End) Then Return trivia @@ -142,6 +144,7 @@ recurse: End If End If Next + Return Nothing End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 9a6dfb6265fca..6f89d8dbd5353 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1014,6 +1014,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices builder.Append(s_dotToken) End If End While + names.Free() ' name (include generic type parameters) @@ -1080,6 +1081,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices End If End If End If + Debug.Assert(name IsNot Nothing, "Unexpected node type " + node.Kind().ToString()) Return name End Function @@ -1092,6 +1094,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices builder.Append(", ") builder.Append(typeParameterList.Parameters(i).Identifier.Text) Next + builder.Append(")"c) End If End Sub @@ -1370,6 +1373,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices If (node.Parent.IsKind(SyntaxKind.FieldDeclaration)) Then Return True End If + Return False Case SyntaxKind.NamespaceStatement, @@ -2296,6 +2300,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Case SyntaxKind.RaiseEventAccessorBlock Return DeclarationKind.RaiseAccessor End Select + Return DeclarationKind.None End Function @@ -2304,6 +2309,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices For i = 0 To nodes.Count - 1 count = count + GetDeclarationCount(nodes(i)) Next + Return count End Function @@ -2322,6 +2328,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Case SyntaxKind.ImportsStatement Return DirectCast(node, ImportsStatementSyntax).ImportsClauses.Count End Select + Return 1 End Function @@ -2336,6 +2343,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Return p.ParameterList IsNot Nothing AndAlso p.ParameterList.Parameters.Count > 0 AndAlso p.Modifiers.Any(SyntaxKind.DefaultKeyword) End If End Select + Return False End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Utilities/CastAnalyzer.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Utilities/CastAnalyzer.vb index 451ce43657070..31939f2043a0b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Utilities/CastAnalyzer.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Utilities/CastAnalyzer.vb @@ -334,6 +334,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions If expressionToCastType.IsWidening Then Return True End If + If expressionToCastType.IsNarrowing AndAlso Not _semanticModel.OptionStrict = OptionStrict.On Then Return True diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index b336edcc5a997..2616a24c40e74 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -1623,6 +1623,7 @@ private static SyntaxNode UnwrapPossibleTuple(SyntaxNode node) node = node.Parent; continue; } + if (node.Parent.IsKind(SyntaxKind.Argument) && node.Parent.Parent.IsKind(SyntaxKind.TupleExpression)) { node = node.Parent.Parent; @@ -1645,6 +1646,7 @@ private static bool IsPossibleVarDeconstructionOpenParenOrComma(SyntaxToken left return true; } } + return false; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/DocumentationCommentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/DocumentationCommentExtensions.cs index e2caec752c8e0..a075030b96b56 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/DocumentationCommentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/DocumentationCommentExtensions.cs @@ -16,6 +16,7 @@ public static bool IsMultilineDocComment([NotNullWhen(true)] this DocumentationC { return false; } + return documentationComment.ToFullString().StartsWith("/**", StringComparison.Ordinal); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs index b72e782fead03..fd0d31a104df4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs @@ -678,6 +678,7 @@ private void DetermineTypeParameterMapping(ITypeSymbol inferredType, ITypeSymbol { result[returnTypeParameter] = inferredType; } + return; } } @@ -705,6 +706,7 @@ private void DetermineTypeParameterMapping(ITypeSymbol inferredType, ITypeSymbol DetermineTypeParameterMapping(inferredNamedType.TypeArguments[i], returnNamedType.TypeArguments[i], result); } } + return; } } @@ -831,6 +833,7 @@ private IEnumerable InferTypeInArrayType(ArrayTypeSyntax arra currentTypes = currentTypes.Select(t => t.InferredType).OfType() .SelectAsArray(a => new TypeInferenceInfo(a.ElementType)); } + return currentTypes; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs index ef4cbce90fe0c..32a6059711167 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs @@ -93,6 +93,12 @@ internal class SyntaxKindSet SyntaxKind.RecordDeclaration, }; + public static readonly ISet ClassRecordTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.ClassDeclaration, + SyntaxKind.RecordDeclaration, + }; + public static readonly ISet ClassStructRecordTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.ClassDeclaration, @@ -106,5 +112,10 @@ internal class SyntaxKindSet SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration, }; + + public static readonly ISet InterfaceOnlyTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.InterfaceDeclaration, + }; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/SyntaxKindExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/SyntaxKindExtensions.vb index fc1116077cd83..916fc163996c9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/SyntaxKindExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/SyntaxKindExtensions.vb @@ -20,6 +20,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions For Each k In kinds If k = kind Then Return True Next + Return False End Function @@ -37,6 +38,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions For i = start To kinds.Length - 1 If kinds(i) = kind Then Return i Next + Return -1 End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb index b63f1d3da171a..f3cf17f4e7803 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb @@ -639,6 +639,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If expressionStatement.Expression.IsKind(SyntaxKind.InvocationExpression) Then Return InferTypeInCallStatement() End If + Return SpecializedCollections.EmptyEnumerable(Of TypeInferenceInfo)() End Function diff --git a/src/Workspaces/VisualBasic/Portable/CaseCorrection/VisualBasicCaseCorrectionService.Rewriter.vb b/src/Workspaces/VisualBasic/Portable/CaseCorrection/VisualBasicCaseCorrectionService.Rewriter.vb index c616b6e755e9c..2b7402542b8c3 100644 --- a/src/Workspaces/VisualBasic/Portable/CaseCorrection/VisualBasicCaseCorrectionService.Rewriter.vb +++ b/src/Workspaces/VisualBasic/Portable/CaseCorrection/VisualBasicCaseCorrectionService.Rewriter.vb @@ -113,6 +113,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CaseCorrection If param Is parameterSyntax Then Exit For End If + ordinal = ordinal + 1 Next diff --git a/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.vb b/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.vb index a8ab5f8f16c48..d34c54f173a17 100644 --- a/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.vb @@ -79,6 +79,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers If TryClassifyIdentifier(node, semanticModel, cancellationToken, classifiedSpan) Then result.Add(classifiedSpan) End If + Return End If diff --git a/src/Workspaces/VisualBasic/Portable/CodeCleanup/AsyncOrIteratorFunctionReturnTypeFixer.vb b/src/Workspaces/VisualBasic/Portable/CodeCleanup/AsyncOrIteratorFunctionReturnTypeFixer.vb index a9b1e2e71a174..cfbd859fec38f 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeCleanup/AsyncOrIteratorFunctionReturnTypeFixer.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeCleanup/AsyncOrIteratorFunctionReturnTypeFixer.vb @@ -102,6 +102,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup Return True End If End If + Return False End Function @@ -141,6 +142,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup End Select End If End If + Return False End Function diff --git a/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/AddMissingTokensCodeCleanupProvider.vb b/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/AddMissingTokensCodeCleanupProvider.vb index 2ec362da2eee6..d1890270c9e77 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/AddMissingTokensCodeCleanupProvider.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/AddMissingTokensCodeCleanupProvider.vb @@ -264,6 +264,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers If n.ParameterList.HasLeadingTrivia Then newParamList = newParamList.WithLeadingTrivia(n.ParameterList.GetLeadingTrivia) End If + If n.ParameterList.HasTrailingTrivia Then newParamList = newParamList.WithTrailingTrivia(n.ParameterList.GetTrailingTrivia) End If @@ -280,6 +281,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers Return nodeWithParams End Function + Return AddParenthesesTransform(node, newNode, nameChecker, Function(n) n.ParameterList, transform) End Function diff --git a/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/RemoveUnnecessaryLineContinuationCodeCleanupProvider.vb b/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/RemoveUnnecessaryLineContinuationCodeCleanupProvider.vb index d4586ad551c83..7394c1197d429 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/RemoveUnnecessaryLineContinuationCodeCleanupProvider.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeCleanup/Providers/RemoveUnnecessaryLineContinuationCodeCleanupProvider.vb @@ -275,6 +275,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers last.Kind <> SyntaxKind.ColonTrivia Then Yield t End If + last = t Next End Function @@ -290,6 +291,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers If colon Then Continue For End If + colon = True End If @@ -301,6 +303,7 @@ Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers If colon Then Continue For End If + colon = True End If diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/EventGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/EventGenerator.vb index 1f263d3cdf181..585f7a84dc30c 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/EventGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/EventGenerator.vb @@ -127,6 +127,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration result = result.WithEventStatement( result.EventStatement.WithImplementsClause(GenerateImplementsClause(explicitInterface))) End If + Return result End Function diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb index 7ab20928f3c0e..03a965eca77db 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb @@ -604,6 +604,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim computeNewModifiersList As Func(Of SyntaxTokenList, SyntaxTokenList) = Function(modifiersList As SyntaxTokenList) Return SyntaxFactory.TokenList(newModifiers) End Function + Return UpdateDeclarationModifiers(declaration, computeNewModifiersList) End Function @@ -611,6 +612,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim computeNewModifiersList As Func(Of SyntaxTokenList, SyntaxTokenList) = Function(modifiersList As SyntaxTokenList) Return UpdateDeclarationAccessibility(modifiersList, newAccessibility, options) End Function + Return UpdateDeclarationModifiers(declaration, computeNewModifiersList) End Function @@ -656,6 +658,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case Else Return asClause End Select + Return asNewClause.WithNewExpression(updatedNewExpression) End Select End Function diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index 7f01fc3b020c4..9d1659d957012 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -756,7 +756,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Public Overrides Function FieldDeclaration(name As String, type As SyntaxNode, Optional accessibility As Accessibility = Nothing, Optional modifiers As DeclarationModifiers = Nothing, Optional initializer As SyntaxNode = Nothing) As SyntaxNode Return SyntaxFactory.FieldDeclaration( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_fieldModifiers, DeclarationKind.Field), + modifiers:=GetModifierList(accessibility, modifiers And s_fieldModifiers, declaration:=Nothing, DeclarationKind.Field), declarators:=SyntaxFactory.SingletonSeparatedList(VisualBasicSyntaxGeneratorInternal.VariableDeclarator(type, name.ToModifiedIdentifier, initializer))) End Function @@ -772,7 +772,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim statement = SyntaxFactory.MethodStatement( kind:=If(returnType Is Nothing, SyntaxKind.SubStatement, SyntaxKind.FunctionStatement), attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_methodModifiers, DeclarationKind.Method), + modifiers:=GetModifierList(accessibility, modifiers And s_methodModifiers, declaration:=Nothing, DeclarationKind.Method), subOrFunctionKeyword:=If(returnType Is Nothing, SyntaxFactory.Token(SyntaxKind.SubKeyword), SyntaxFactory.Token(SyntaxKind.FunctionKeyword)), identifier:=identifier.ToIdentifierToken(), typeParameterList:=GetTypeParameters(typeParameters), @@ -803,7 +803,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim asClause = If(returnType IsNot Nothing, SyntaxFactory.SimpleAsClause(DirectCast(returnType, TypeSyntax)), Nothing) Dim parameterList = GetParameterList(parameters) Dim operatorToken = SyntaxFactory.Token(GetTokenKind(kind)) - Dim modifierList As SyntaxTokenList = GetModifierList(accessibility, modifiers And s_methodModifiers, DeclarationKind.Operator) + Dim modifierList As SyntaxTokenList = GetModifierList(accessibility, modifiers And s_methodModifiers, declaration:=Nothing, DeclarationKind.Operator) If kind = OperatorKind.ImplicitConversion OrElse kind = OperatorKind.ExplicitConversion Then modifierList = modifierList.Add(SyntaxFactory.Token( @@ -898,21 +898,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration If initializer IsNot Nothing Then tokens = tokens.Add(SyntaxFactory.Token(SyntaxKind.OptionalKeyword)) End If + If refKind <> RefKind.None Then tokens = tokens.Add(SyntaxFactory.Token(SyntaxKind.ByRefKeyword)) End If + Return tokens End Function Public Overrides Function GetAccessorDeclaration(Optional accessibility As Accessibility = Accessibility.NotApplicable, Optional statements As IEnumerable(Of SyntaxNode) = Nothing) As SyntaxNode Return SyntaxFactory.GetAccessorBlock( - SyntaxFactory.GetAccessorStatement().WithModifiers(GetModifierList(accessibility, DeclarationModifiers.None, DeclarationKind.Property)), + SyntaxFactory.GetAccessorStatement().WithModifiers(GetModifierList(accessibility, DeclarationModifiers.None, declaration:=Nothing, DeclarationKind.Property)), GetStatementList(statements)) End Function Public Overrides Function SetAccessorDeclaration(Optional accessibility As Accessibility = Accessibility.NotApplicable, Optional statements As IEnumerable(Of SyntaxNode) = Nothing) As SyntaxNode Return SyntaxFactory.SetAccessorBlock( - SyntaxFactory.SetAccessorStatement().WithModifiers(GetModifierList(accessibility, DeclarationModifiers.None, DeclarationKind.Property)), + SyntaxFactory.SetAccessorStatement().WithModifiers(GetModifierList(accessibility, DeclarationModifiers.None, declaration:=Nothing, DeclarationKind.Property)), GetStatementList(statements)) End Function @@ -966,7 +968,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim asClause = SyntaxFactory.SimpleAsClause(DirectCast(type, TypeSyntax)) Dim statement = SyntaxFactory.PropertyStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_propertyModifiers, DeclarationKind.Property), + modifiers:=GetModifierList(accessibility, modifiers And s_propertyModifiers, declaration:=Nothing, DeclarationKind.Property), identifier:=identifier.ToIdentifierToken(), parameterList:=Nothing, asClause:=asClause, @@ -1004,7 +1006,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim asClause = SyntaxFactory.SimpleAsClause(DirectCast(type, TypeSyntax)) Dim statement = SyntaxFactory.PropertyStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_indexerModifiers, DeclarationKind.Indexer, isDefault:=True), + modifiers:=GetModifierList(accessibility, modifiers And s_indexerModifiers, declaration:=Nothing, DeclarationKind.Indexer, isDefault:=True), identifier:=SyntaxFactory.Identifier("Item"), parameterList:=SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters.Cast(Of ParameterSyntax))), asClause:=asClause, @@ -1177,6 +1179,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return qname.Right.ToString() End If End If + Return GetName(declaration) End Function @@ -1249,7 +1252,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim prop = TryCast(declaration, PropertyStatementSyntax) If prop IsNot Nothing Then - prop = prop.WithModifiers(WithIsDefault(prop.Modifiers, GetIsDefault(prop.Modifiers) And allowDefault, GetDeclarationKind(declaration))) + prop = prop.WithModifiers(WithIsDefault(prop.Modifiers, GetIsDefault(prop.Modifiers) And allowDefault, declaration)) Dim accessors = New List(Of AccessorBlockSyntax) accessors.Add(CreateGetAccessorBlock(Nothing)) @@ -1277,7 +1280,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return isDefault End Function - Private Function WithIsDefault(modifierList As SyntaxTokenList, isDefault As Boolean, kind As DeclarationKind) As SyntaxTokenList + Private Function WithIsDefault(modifierList As SyntaxTokenList, isDefault As Boolean, declaration As SyntaxNode) As SyntaxTokenList Dim access As Accessibility Dim modifiers As DeclarationModifiers Dim currentIsDefault As Boolean @@ -1285,7 +1288,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration SyntaxFacts.GetAccessibilityAndModifiers(modifierList, access, modifiers, currentIsDefault) If currentIsDefault <> isDefault Then - Return GetModifierList(access, modifiers, kind, isDefault) + Return GetModifierList(access, modifiers, declaration, GetDeclarationKind(declaration), isDefault) Else Return modifierList End If @@ -1309,7 +1312,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.ConstructorBlock( subNewStatement:=SyntaxFactory.SubNewStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_constructorModifiers, DeclarationKind.Constructor), + modifiers:=GetModifierList(accessibility, modifiers And s_constructorModifiers, declaration:=Nothing, DeclarationKind.Constructor), parameterList:=If(parameters IsNot Nothing, SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters.Cast(Of ParameterSyntax)())), SyntaxFactory.ParameterList())), statements:=stats) End Function @@ -1331,7 +1334,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.ClassBlock( classStatement:=SyntaxFactory.ClassStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_classModifiers, DeclarationKind.Class), + modifiers:=GetModifierList(accessibility, modifiers And s_classModifiers, declaration:=Nothing, DeclarationKind.Class), identifier:=name.ToIdentifierToken(), typeParameterList:=GetTypeParameters(typeParameters)), [inherits]:=If(baseType IsNot Nothing, SyntaxFactory.SingletonList(SyntaxFactory.InheritsStatement(DirectCast(baseType, TypeSyntax))), Nothing), @@ -1367,7 +1370,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.StructureBlock( structureStatement:=SyntaxFactory.StructureStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And s_structModifiers, DeclarationKind.Struct), + modifiers:=GetModifierList(accessibility, modifiers And s_structModifiers, declaration:=Nothing, DeclarationKind.Struct), identifier:=name.ToIdentifierToken(), typeParameterList:=GetTypeParameters(typeParameters)), [inherits]:=Nothing, @@ -1390,7 +1393,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.InterfaceBlock( interfaceStatement:=SyntaxFactory.InterfaceStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, DeclarationModifiers.None, DeclarationKind.Interface), + modifiers:=GetModifierList(accessibility, DeclarationModifiers.None, declaration:=Nothing, DeclarationKind.Interface), identifier:=name.ToIdentifierToken(), typeParameterList:=GetTypeParameters(typeParameters)), [inherits]:=If(itypes IsNot Nothing, SyntaxFactory.SingletonList(SyntaxFactory.InheritsStatement(SyntaxFactory.SeparatedList(itypes))), Nothing), @@ -1455,7 +1458,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.EnumBlock( enumStatement:=SyntaxFactory.EnumStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(SyntaxKind.EnumStatement), DeclarationKind.Enum), + modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(SyntaxKind.EnumStatement), declaration:=Nothing, DeclarationKind.Enum), identifier:=name.ToIdentifierToken(), underlyingType:=underlyingTypeClause), members:=AsEnumMembers(members)) @@ -1507,7 +1510,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.DelegateStatement( kind:=kind, attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(kind), DeclarationKind.Delegate), + modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(kind), declaration:=Nothing, DeclarationKind.Delegate), subOrFunctionKeyword:=If(kind = SyntaxKind.DelegateSubStatement, SyntaxFactory.Token(SyntaxKind.SubKeyword), SyntaxFactory.Token(SyntaxKind.FunctionKeyword)), identifier:=name.ToIdentifierToken(), typeParameterList:=GetTypeParameters(typeParameters), @@ -1528,6 +1531,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration If name IsNot Nothing Then Return Me.NamespaceImportDeclaration(name) End If + Return TryCast(node, ImportsStatementSyntax) End Function @@ -1547,6 +1551,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration CType(name, NameSyntax)))) End If + Throw New ArgumentException("name is not a NameSyntax.", NameOf(name)) End Function @@ -1714,6 +1719,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return asClause.Attributes End Select End If + Return Nothing End Function @@ -1857,6 +1863,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return declaration.Parent End If End Select + Return declaration End Function @@ -1879,6 +1886,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return stmt.WithImportsClauses(SyntaxFactory.SingletonSeparatedList(DirectCast(declaration, ImportsClauseSyntax))) End If End Select + Return declaration End Function @@ -1973,6 +1981,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case SyntaxKind.SimpleImportsClause Return DirectCast(declaration, SimpleImportsClauseSyntax).Name.ToString() End Select + Return String.Empty End Function @@ -2061,6 +2070,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return asClause.Type End If End Select + Return Nothing End Function @@ -2138,6 +2148,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return vd.AsClause End If End Select + Return Nothing End Function @@ -2172,6 +2183,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case SyntaxKind.VariableDeclarator Return DirectCast(declaration, VariableDeclaratorSyntax).WithAsClause(asClause) End Select + Return declaration End Function @@ -2366,8 +2378,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim isDefault As Boolean SyntaxFacts.GetAccessibilityAndModifiers(tokens, acc, currentMods, isDefault) - If (currentMods <> modifiers) Then - Dim newTokens = GetModifierList(acc, modifiers And GetAllowedModifiers(declaration.Kind), GetDeclarationKind(declaration), isDefault) + If currentMods <> modifiers Then + Dim newTokens = GetModifierList(acc, modifiers And GetAllowedModifiers(declaration.Kind), declaration, GetDeclarationKind(declaration), isDefault) Return WithModifierTokens(declaration, Merge(tokens, newTokens)) Else Return declaration @@ -2469,7 +2481,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return declaration End If - Dim newTokens = GetModifierList(accessibility, mods, GetDeclarationKind(declaration), isDefault) + Dim newTokens = GetModifierList(accessibility, mods, declaration, GetDeclarationKind(declaration), isDefault) 'GetDeclarationKind returns None for Field if the count is > 1 'To handle multiple declarations on a field if the Accessibility is NotApplicable, we need to add the Dim If declaration.Kind = SyntaxKind.FieldDeclaration AndAlso accessibility = Accessibility.NotApplicable AndAlso newTokens.Count = 0 Then @@ -2480,7 +2492,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return WithModifierTokens(declaration, Merge(tokens, newTokens)) End Function - Private Shared Function GetModifierList(accessibility As Accessibility, modifiers As DeclarationModifiers, kind As DeclarationKind, Optional isDefault As Boolean = False) As SyntaxTokenList + Private Shared Function GetModifierList(accessibility As Accessibility, modifiers As DeclarationModifiers, declaration As SyntaxNode, kind As DeclarationKind, Optional isDefault As Boolean = False) As SyntaxTokenList Dim _list = SyntaxFactory.TokenList() ' While partial must always be last in C#, its preferred position in VB is to be first, @@ -2511,8 +2523,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Throw New NotSupportedException(String.Format("Accessibility '{0}' not supported.", accessibility)) End Select + Dim isClass = kind = DeclarationKind.Class OrElse declaration.IsKind(SyntaxKind.ClassStatement) If modifiers.IsAbstract Then - If kind = DeclarationKind.Class Then + If isClass Then _list = _list.Add(SyntaxFactory.Token(SyntaxKind.MustInheritKeyword)) Else _list = _list.Add(SyntaxFactory.Token(SyntaxKind.MustOverrideKeyword)) @@ -2524,7 +2537,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration End If If modifiers.IsSealed Then - If kind = DeclarationKind.Class Then + If isClass Then _list = _list.Add(SyntaxFactory.Token(SyntaxKind.NotInheritableKeyword)) Else _list = _list.Add(SyntaxFactory.Token(SyntaxKind.NotOverridableKeyword)) @@ -2790,6 +2803,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return ev.Value End If End Select + Return Nothing End Function @@ -2798,6 +2812,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration If es IsNot Nothing Then Return es.Expression End If + Return DirectCast(node, ExpressionSyntax) End Function @@ -2841,6 +2856,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return WithEqualsValue(declaration, SyntaxFactory.EqualsValue(expr)) End If End Select + Return declaration End Function @@ -2861,6 +2877,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case SyntaxKind.VariableDeclarator Return DirectCast(declaration, VariableDeclaratorSyntax).Initializer End Select + Return Nothing End Function @@ -2879,6 +2896,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return ReplaceWithTrivia(declaration, fd.Declarators(0), fd.Declarators(0).WithInitializer(ev)) End If End Select + Return declaration End Function @@ -3197,13 +3215,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return eb.WithAccessors(eb.Accessors.Add(accessor)) End Select End If + Return declaration End Function Public Overrides Function EventDeclaration(name As String, type As SyntaxNode, Optional accessibility As Accessibility = Accessibility.NotApplicable, Optional modifiers As DeclarationModifiers = Nothing) As SyntaxNode Return SyntaxFactory.EventStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(SyntaxKind.EventStatement), DeclarationKind.Event), + modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(SyntaxKind.EventStatement), declaration:=Nothing, DeclarationKind.Event), customKeyword:=Nothing, eventKeyword:=SyntaxFactory.Token(SyntaxKind.EventKeyword), identifier:=name.ToIdentifierToken(), @@ -3243,9 +3262,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration If addAccessorStatements Is Nothing Then addAccessorStatements = SpecializedCollections.EmptyEnumerable(Of SyntaxNode)() End If + If removeAccessorStatements Is Nothing Then removeAccessorStatements = SpecializedCollections.EmptyEnumerable(Of SyntaxNode)() End If + If raiseAccessorStatements Is Nothing Then raiseAccessorStatements = SpecializedCollections.EmptyEnumerable(Of SyntaxNode)() End If @@ -3257,7 +3278,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim evStatement = SyntaxFactory.EventStatement( attributeLists:=Nothing, - modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(SyntaxKind.EventStatement), DeclarationKind.Event), + modifiers:=GetModifierList(accessibility, modifiers And GetAllowedModifiers(SyntaxKind.EventStatement), declaration:=Nothing, DeclarationKind.Event), customKeyword:=SyntaxFactory.Token(SyntaxKind.CustomKeyword), eventKeyword:=SyntaxFactory.Token(SyntaxKind.EventKeyword), identifier:=name.ToIdentifierToken(), @@ -3307,6 +3328,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case SyntaxKind.Attribute Return DirectCast(declaration, AttributeSyntax).ArgumentList End Select + Return Nothing End Function @@ -3320,6 +3342,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case SyntaxKind.Attribute Return DirectCast(declaration, AttributeSyntax).WithArgumentList(argumentList) End Select + Return declaration End Function diff --git a/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb b/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb index 08d4b155a0393..bede58f987611 100644 --- a/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb +++ b/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb @@ -5,6 +5,7 @@ Imports System.Collections.Immutable Imports System.Composition Imports System.Text +Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Host.Mef @@ -16,7 +17,13 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Friend Class VisualBasicDeclaredSymbolInfoFactoryService - Inherits AbstractDeclaredSymbolInfoFactoryService + Inherits AbstractDeclaredSymbolInfoFactoryService(Of + CompilationUnitSyntax, + ImportsStatementSyntax, + NamespaceBlockSyntax, + TypeBlockSyntax, + EnumBlockSyntax, + StatementSyntax) Private Const ExtensionName As String = "Extension" Private Const ExtensionAttributeName As String = "ExtensionAttribute" @@ -108,15 +115,25 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Return simpleName.Identifier.ValueText End Function - Private Shared Function GetContainerDisplayName(node As SyntaxNode) As String + Protected Overrides Function GetContainerDisplayName(node As StatementSyntax) As String Return VisualBasicSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeTypeParameters) End Function - Private Shared Function GetFullyQualifiedContainerName(node As SyntaxNode, rootNamespace As String) As String + Protected Overrides Function GetFullyQualifiedContainerName(node As StatementSyntax, rootNamespace As String) As String Return VisualBasicSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeNamespaces, rootNamespace) End Function - Public Overrides Function TryGetDeclaredSymbolInfo(stringTable As StringTable, node As SyntaxNode, rootNamespace As String, ByRef declaredSymbolInfo As DeclaredSymbolInfo) As Boolean + Protected Overrides Sub AddDeclaredSymbolInfosWorker( + container As SyntaxNode, + node As StatementSyntax, + stringTable As StringTable, + declaredSymbolInfos As ArrayBuilder(Of DeclaredSymbolInfo), + aliases As Dictionary(Of String, String), + extensionMethodInfo As Dictionary(Of String, ArrayBuilder(Of Integer)), + containerDisplayName As String, + fullyQualifiedContainerName As String, + cancellationToken As CancellationToken) + ' If this Is a part of partial type that only contains nested types, then we don't make an info type for it. ' That's because we effectively think of this as just being a virtual container just to hold the nested ' types, And Not something someone would want to explicitly navigate to itself. Similar to how we think of @@ -127,171 +144,199 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols typeDecl.Members.Any() AndAlso typeDecl.Members.All(Function(m) TypeOf m Is TypeBlockSyntax) Then - declaredSymbolInfo = Nothing - Return False + Return + End If + + If node.Kind() = SyntaxKind.PropertyBlock Then + node = DirectCast(node, PropertyBlockSyntax).PropertyStatement + ElseIf node.Kind() = SyntaxKind.EventBlock Then + node = DirectCast(node, EventBlockSyntax).EventStatement + ElseIf TypeOf node Is MethodBlockBaseSyntax Then + node = DirectCast(node, MethodBlockBaseSyntax).BlockStatement End If Dim kind = node.Kind() Select Case kind Case SyntaxKind.ClassBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ModuleBlock, SyntaxKind.StructureBlock Dim typeBlock = CType(node, TypeBlockSyntax) - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, typeDecl.BlockStatement.Identifier.ValueText, GetTypeParameterSuffix(typeBlock.BlockStatement.TypeParameterList), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent, rootNamespace), + containerDisplayName, + fullyQualifiedContainerName, typeBlock.BlockStatement.Modifiers.Any(SyntaxKind.PartialKeyword), If(kind = SyntaxKind.ClassBlock, DeclaredSymbolInfoKind.Class, If(kind = SyntaxKind.InterfaceBlock, DeclaredSymbolInfoKind.Interface, If(kind = SyntaxKind.ModuleBlock, DeclaredSymbolInfoKind.Module, DeclaredSymbolInfoKind.Struct))), - GetAccessibility(typeBlock, typeBlock.BlockStatement.Modifiers), + GetAccessibility(container, typeBlock, typeBlock.BlockStatement.Modifiers), typeBlock.BlockStatement.Identifier.Span, GetInheritanceNames(stringTable, typeBlock), - IsNestedType(typeBlock)) - Return True + IsNestedType(typeBlock))) + Return Case SyntaxKind.EnumBlock - Dim enumDecl = CType(node, EnumBlockSyntax) - declaredSymbolInfo = DeclaredSymbolInfo.Create( + Dim enumDecl = DirectCast(node, EnumBlockSyntax) + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, enumDecl.EnumStatement.Identifier.ValueText, Nothing, - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent, rootNamespace), + containerDisplayName, + fullyQualifiedContainerName, enumDecl.EnumStatement.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Enum, - GetAccessibility(enumDecl, enumDecl.EnumStatement.Modifiers), + GetAccessibility(container, enumDecl, enumDecl.EnumStatement.Modifiers), enumDecl.EnumStatement.Identifier.Span, ImmutableArray(Of String).Empty, - IsNestedType(enumDecl)) - Return True - Case SyntaxKind.ConstructorBlock - Dim constructor = CType(node, ConstructorBlockSyntax) - Dim typeBlock = TryCast(constructor.Parent, TypeBlockSyntax) + IsNestedType(enumDecl))) + Return + Case SyntaxKind.SubNewStatement + Dim constructor = DirectCast(node, SubNewStatementSyntax) + Dim typeBlock = TryCast(container, TypeBlockSyntax) If typeBlock IsNot Nothing Then - declaredSymbolInfo = DeclaredSymbolInfo.Create( + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, typeBlock.BlockStatement.Identifier.ValueText, GetConstructorSuffix(constructor), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent, rootNamespace), - constructor.SubNewStatement.Modifiers.Any(SyntaxKind.PartialKeyword), + containerDisplayName, + fullyQualifiedContainerName, + constructor.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Constructor, - GetAccessibility(constructor, constructor.SubNewStatement.Modifiers), - constructor.SubNewStatement.NewKeyword.Span, + GetAccessibility(container, constructor, constructor.Modifiers), + constructor.NewKeyword.Span, ImmutableArray(Of String).Empty, - parameterCount:=If(constructor.SubNewStatement.ParameterList?.Parameters.Count, 0)) + parameterCount:=If(constructor.ParameterList?.Parameters.Count, 0))) - Return True + Return End If Case SyntaxKind.DelegateFunctionStatement, SyntaxKind.DelegateSubStatement - Dim delegateDecl = CType(node, DelegateStatementSyntax) - declaredSymbolInfo = DeclaredSymbolInfo.Create( + Dim delegateDecl = DirectCast(node, DelegateStatementSyntax) + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, delegateDecl.Identifier.ValueText, GetTypeParameterSuffix(delegateDecl.TypeParameterList), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent, rootNamespace), + containerDisplayName, + fullyQualifiedContainerName, delegateDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Delegate, - GetAccessibility(delegateDecl, delegateDecl.Modifiers), + GetAccessibility(container, delegateDecl, delegateDecl.Modifiers), delegateDecl.Identifier.Span, - ImmutableArray(Of String).Empty) - Return True + ImmutableArray(Of String).Empty)) + Return Case SyntaxKind.EnumMemberDeclaration - Dim enumMember = CType(node, EnumMemberDeclarationSyntax) - declaredSymbolInfo = DeclaredSymbolInfo.Create( + Dim enumMember = DirectCast(node, EnumMemberDeclarationSyntax) + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, enumMember.Identifier.ValueText, Nothing, - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent, rootNamespace), + containerDisplayName, + fullyQualifiedContainerName, isPartial:=False, DeclaredSymbolInfoKind.EnumMember, Accessibility.Public, enumMember.Identifier.Span, - ImmutableArray(Of String).Empty) - Return True + ImmutableArray(Of String).Empty)) + Return Case SyntaxKind.EventStatement - Dim eventDecl = CType(node, EventStatementSyntax) - Dim statementOrBlock = If(TypeOf node.Parent Is EventBlockSyntax, node.Parent, node) - Dim eventParent = statementOrBlock.Parent - declaredSymbolInfo = DeclaredSymbolInfo.Create( + Dim eventDecl = DirectCast(node, EventStatementSyntax) + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, eventDecl.Identifier.ValueText, Nothing, - GetContainerDisplayName(eventParent), - GetFullyQualifiedContainerName(eventParent, rootNamespace), + containerDisplayName, + fullyQualifiedContainerName, eventDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Event, - GetAccessibility(statementOrBlock, eventDecl.Modifiers), + GetAccessibility(container, eventDecl, eventDecl.Modifiers), eventDecl.Identifier.Span, - ImmutableArray(Of String).Empty) - Return True - Case SyntaxKind.FunctionBlock, SyntaxKind.SubBlock - Dim funcDecl = CType(node, MethodBlockSyntax) - declaredSymbolInfo = DeclaredSymbolInfo.Create( + ImmutableArray(Of String).Empty)) + Return + Case SyntaxKind.FunctionStatement, SyntaxKind.SubStatement + Dim funcDecl = DirectCast(node, MethodStatementSyntax) + Dim isExtension = IsExtensionMethod(funcDecl) + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, - funcDecl.SubOrFunctionStatement.Identifier.ValueText, + funcDecl.Identifier.ValueText, GetMethodSuffix(funcDecl), - GetContainerDisplayName(node.Parent), - GetFullyQualifiedContainerName(node.Parent, rootNamespace), - funcDecl.SubOrFunctionStatement.Modifiers.Any(SyntaxKind.PartialKeyword), - If(IsExtensionMethod(funcDecl), DeclaredSymbolInfoKind.ExtensionMethod, DeclaredSymbolInfoKind.Method), - GetAccessibility(node, funcDecl.SubOrFunctionStatement.Modifiers), - funcDecl.SubOrFunctionStatement.Identifier.Span, + containerDisplayName, + fullyQualifiedContainerName, + funcDecl.Modifiers.Any(SyntaxKind.PartialKeyword), + If(isExtension, DeclaredSymbolInfoKind.ExtensionMethod, DeclaredSymbolInfoKind.Method), + GetAccessibility(container, funcDecl, funcDecl.Modifiers), + funcDecl.Identifier.Span, ImmutableArray(Of String).Empty, - parameterCount:=If(funcDecl.SubOrFunctionStatement.ParameterList?.Parameters.Count, 0), - typeParameterCount:=If(funcDecl.SubOrFunctionStatement.TypeParameterList?.Parameters.Count, 0)) - Return True - Case SyntaxKind.ModifiedIdentifier - Dim modifiedIdentifier = CType(node, ModifiedIdentifierSyntax) - Dim variableDeclarator = TryCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax) - Dim fieldDecl = TryCast(variableDeclarator?.Parent, FieldDeclarationSyntax) - If fieldDecl IsNot Nothing Then - declaredSymbolInfo = DeclaredSymbolInfo.Create( - stringTable, - modifiedIdentifier.Identifier.ValueText, Nothing, - GetContainerDisplayName(fieldDecl.Parent), - GetFullyQualifiedContainerName(fieldDecl.Parent, rootNamespace), - fieldDecl.Modifiers.Any(SyntaxKind.PartialKeyword), - If(fieldDecl.Modifiers.Any(Function(m) m.Kind() = SyntaxKind.ConstKeyword), - DeclaredSymbolInfoKind.Constant, - DeclaredSymbolInfoKind.Field), - GetAccessibility(fieldDecl, fieldDecl.Modifiers), - modifiedIdentifier.Identifier.Span, - ImmutableArray(Of String).Empty) - Return True + parameterCount:=If(funcDecl.ParameterList?.Parameters.Count, 0), + typeParameterCount:=If(funcDecl.TypeParameterList?.Parameters.Count, 0))) + If isExtension Then + AddExtensionMethodInfo(funcDecl, aliases, declaredSymbolInfos.Count - 1, extensionMethodInfo) End If + + Return Case SyntaxKind.PropertyStatement - Dim propertyDecl = CType(node, PropertyStatementSyntax) - Dim statementOrBlock = If(TypeOf node.Parent Is PropertyBlockSyntax, node.Parent, node) - Dim propertyParent = statementOrBlock.Parent - declaredSymbolInfo = DeclaredSymbolInfo.Create( + Dim propertyDecl = DirectCast(node, PropertyStatementSyntax) + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( stringTable, propertyDecl.Identifier.ValueText, GetPropertySuffix(propertyDecl), - GetContainerDisplayName(propertyParent), - GetFullyQualifiedContainerName(propertyParent, rootNamespace), + containerDisplayName, + fullyQualifiedContainerName, propertyDecl.Modifiers.Any(SyntaxKind.PartialKeyword), DeclaredSymbolInfoKind.Property, - GetAccessibility(statementOrBlock, propertyDecl.Modifiers), + GetAccessibility(container, propertyDecl, propertyDecl.Modifiers), propertyDecl.Identifier.Span, - ImmutableArray(Of String).Empty) - Return True + ImmutableArray(Of String).Empty)) + Return + Case SyntaxKind.FieldDeclaration + Dim fieldDecl = DirectCast(node, FieldDeclarationSyntax) + For Each variableDeclarator In fieldDecl.Declarators + For Each modifiedIdentifier In variableDeclarator.Names + declaredSymbolInfos.Add(DeclaredSymbolInfo.Create( + stringTable, + modifiedIdentifier.Identifier.ValueText, Nothing, + containerDisplayName, + fullyQualifiedContainerName, + fieldDecl.Modifiers.Any(SyntaxKind.PartialKeyword), + If(fieldDecl.Modifiers.Any(Function(m) m.Kind() = SyntaxKind.ConstKeyword), + DeclaredSymbolInfoKind.Constant, + DeclaredSymbolInfoKind.Field), + GetAccessibility(container, fieldDecl, fieldDecl.Modifiers), + modifiedIdentifier.Identifier.Span, + ImmutableArray(Of String).Empty)) + Next + Next End Select + End Sub - declaredSymbolInfo = Nothing - Return False + Protected Overrides Function GetChildren(node As CompilationUnitSyntax) As SyntaxList(Of StatementSyntax) + Return node.Members + End Function + + Protected Overrides Function GetChildren(node As NamespaceBlockSyntax) As SyntaxList(Of StatementSyntax) + Return node.Members End Function - Private Shared Function IsExtensionMethod(node As MethodBlockSyntax) As Boolean - Dim parameterCount = node.SubOrFunctionStatement.ParameterList?.Parameters.Count + Protected Overrides Function GetChildren(node As TypeBlockSyntax) As SyntaxList(Of StatementSyntax) + Return node.Members + End Function + + Protected Overrides Function GetChildren(node As EnumBlockSyntax) As IEnumerable(Of StatementSyntax) + Return node.Members + End Function + + Protected Overrides Function GetUsingAliases(node As CompilationUnitSyntax) As SyntaxList(Of ImportsStatementSyntax) + Return node.Imports + End Function + + Protected Overrides Function GetUsingAliases(node As NamespaceBlockSyntax) As SyntaxList(Of ImportsStatementSyntax) + Return Nothing + End Function + + Private Shared Function IsExtensionMethod(node As MethodStatementSyntax) As Boolean + Dim parameterCount = node.ParameterList?.Parameters.Count ' Extension method must have at least one parameter and declared inside a module - If Not parameterCount.HasValue OrElse parameterCount.Value = 0 OrElse TypeOf node.Parent IsNot ModuleBlockSyntax Then + If Not parameterCount.HasValue OrElse parameterCount.Value = 0 OrElse TypeOf node.Parent?.Parent IsNot ModuleBlockSyntax Then Return False End If - For Each attributeList In node.BlockStatement.AttributeLists + For Each attributeList In node.AttributeLists For Each attribute In attributeList.Attributes ' ExtensionAttribute takes no argument. If attribute.ArgumentList?.Arguments.Count > 0 Then @@ -312,7 +357,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Return TypeOf node.Parent Is TypeBlockSyntax End Function - Private Shared Function GetAccessibility(node As SyntaxNode, modifiers As SyntaxTokenList) As Accessibility + Private Shared Function GetAccessibility(container As SyntaxNode, node As StatementSyntax, modifiers As SyntaxTokenList) As Accessibility Dim sawFriend = False For Each modifier In modifiers @@ -331,7 +376,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols End If ' No accessibility modifiers - Select Case node.Parent.Kind() + Select Case container.Kind() Case SyntaxKind.ClassBlock ' In a class, fields and shared-constructors are private by default, ' everything Else Is Public @@ -339,13 +384,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Return Accessibility.Private End If - If node.Kind() = SyntaxKind.ConstructorBlock AndAlso - DirectCast(node, ConstructorBlockSyntax).SubNewStatement.Modifiers.Any(SyntaxKind.SharedKeyword) Then + If node.Kind() = SyntaxKind.SubNewStatement AndAlso + DirectCast(node, SubNewStatementSyntax).Modifiers.Any(SyntaxKind.SharedKeyword) Then Return Accessibility.Private End If Return Accessibility.Public - Case SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ModuleBlock ' Everything in a struct/interface/module is public Return Accessibility.Public @@ -355,13 +399,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Return Accessibility.Internal End Function - Private Shared Function GetMethodSuffix(method As MethodBlockSyntax) As String - Return GetTypeParameterSuffix(method.SubOrFunctionStatement.TypeParameterList) & - GetSuffix(method.SubOrFunctionStatement.ParameterList) + Private Shared Function GetMethodSuffix(method As MethodStatementSyntax) As String + Return GetTypeParameterSuffix(method.TypeParameterList) & GetSuffix(method.ParameterList) End Function - Private Shared Function GetConstructorSuffix(method As ConstructorBlockSyntax) As String - Return ".New" & GetSuffix(method.SubNewStatement.ParameterList) + Private Shared Function GetConstructorSuffix(method As SubNewStatementSyntax) As String + Return ".New" & GetSuffix(method.ParameterList) End Function Private Shared Function GetPropertySuffix([property] As PropertyStatementSyntax) As String @@ -422,6 +465,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols If parameterList IsNot Nothing Then AppendParameters(parameterList.Parameters, builder) End If + builder.Append(")"c) Return pooledBuilder.ToStringAndFree() @@ -449,24 +493,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Next End Sub - Public Overrides Function GetReceiverTypeName(node As SyntaxNode) As String - Dim funcDecl = CType(node, MethodBlockSyntax) + Protected Overrides Function GetReceiverTypeName(node As StatementSyntax) As String + Dim funcDecl = DirectCast(node, MethodStatementSyntax) Debug.Assert(IsExtensionMethod(funcDecl)) - Dim typeParameterNames = funcDecl.SubOrFunctionStatement.TypeParameterList?.Parameters.SelectAsArray(Function(p) p.Identifier.Text) + Dim typeParameterNames = funcDecl.TypeParameterList?.Parameters.SelectAsArray(Function(p) p.Identifier.Text) Dim targetTypeName As String = Nothing Dim isArray As Boolean = False - TryGetSimpleTypeNameWorker(funcDecl.BlockStatement.ParameterList.Parameters(0).AsClause?.Type, typeParameterNames, targetTypeName, isArray) + TryGetSimpleTypeNameWorker(funcDecl.ParameterList.Parameters(0).AsClause?.Type, typeParameterNames, targetTypeName, isArray) Return CreateReceiverTypeString(targetTypeName, isArray) End Function - Public Overrides Function TryGetAliasesFromUsingDirective(node As SyntaxNode, ByRef aliases As ImmutableArray(Of (aliasName As String, name As String))) As Boolean - - Dim importStatement = TryCast(node, ImportsStatementSyntax) + Protected Overrides Function TryGetAliasesFromUsingDirective(importStatement As ImportsStatementSyntax, ByRef aliases As ImmutableArray(Of (aliasName As String, name As String))) As Boolean Dim builder = ArrayBuilder(Of (String, String)).GetInstance() - If (importStatement IsNot Nothing) Then + If importStatement IsNot Nothing Then For Each importsClause In importStatement.ImportsClauses If importsClause.Kind = SyntaxKind.SimpleImportsClause Then @@ -511,7 +553,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols Dim genericName = DirectCast(node, GenericNameSyntax) Dim name = genericName.Identifier.Text Dim arity = genericName.Arity - simpleTypeName = If(arity = 0, name, name + GetMetadataAritySuffix(arity)) + simpleTypeName = If(arity = 0, name, name + ArityUtilities.GetMetadataAritySuffix(arity)) Return True ElseIf TypeOf node Is QualifiedNameSyntax Then @@ -576,7 +618,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols End Select End Function - Public Overrides Function GetRootNamespace(compilationOptions As CompilationOptions) As String + Protected Overrides Function GetRootNamespace(compilationOptions As CompilationOptions) As String Return DirectCast(compilationOptions, VisualBasicCompilationOptions).RootNamespace End Function End Class diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/FormattingHelpers.vb b/src/Workspaces/VisualBasic/Portable/Formatting/FormattingHelpers.vb index 52bce4c858560..78f0fdc4232d9 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/FormattingHelpers.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/FormattingHelpers.vb @@ -95,6 +95,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.LessThanToken = token Then Return True End If + Return False End If @@ -105,6 +106,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlMemberAccess.Token2 = token Then Return True End If + Return False End If @@ -115,6 +117,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.ColonToken = token Then Return True End If + Return False End If @@ -125,6 +128,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.EqualsToken = token Then Return True End If + Return False End If @@ -135,6 +139,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.EqualsToken = token Then Return True End If + Return False End If @@ -145,6 +150,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.EqualsToken = token Then Return True End If + Return False End If @@ -155,6 +161,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.LessThanToken = token Then Return True End If + Return False End If @@ -165,6 +172,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.GreaterThanToken = token Then Return True End If + Return False End If @@ -175,6 +183,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlBracketedName.LessThanToken = token Then Return True End If + Return False End If @@ -185,6 +194,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlBracketedName.GreaterThanToken = token Then Return True End If + Return False End If @@ -199,6 +209,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If xmlElement.StartTag.LessThanToken = token Then Return True End If + Return False End If @@ -210,6 +221,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting IsGreaterThanInXmlTag(xmlElement.EndTag, token) Then Return True End If + Return False End If diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/Rules/AlignTokensFormattingRule.vb b/src/Workspaces/VisualBasic/Portable/Formatting/Rules/AlignTokensFormattingRule.vb index be9c40376ee32..c8e10ad8ce202 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/Rules/AlignTokensFormattingRule.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/Rules/AlignTokensFormattingRule.vb @@ -24,6 +24,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If tokens.Count > 1 Then AddAlignIndentationOfTokensToBaseTokenOperation(operations, queryExpression, tokens(0), tokens.Skip(1)) End If + Return End If End Sub diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/Rules/NodeBasedFormattingRule.vb b/src/Workspaces/VisualBasic/Portable/Formatting/Rules/NodeBasedFormattingRule.vb index c9c763d8b0044..b429560d3d29e 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/Rules/NodeBasedFormattingRule.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/Rules/NodeBasedFormattingRule.vb @@ -282,6 +282,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting foundXmlElement = True Exit While End If + previousToken = previousToken.GetPreviousToken(includeZeroWidth:=True) End While @@ -515,8 +516,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If forBlock Is node Then Return count + 1 End If + count = count + 1 Next + Return count End Function End Class diff --git a/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.Indenter.vb b/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.Indenter.vb index de3ec26cd0c74..bd37ccfb9a1a2 100644 --- a/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.Indenter.vb +++ b/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.Indenter.vb @@ -135,6 +135,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Indentation (containingToken.IsKind(SyntaxKind.CloseBraceToken) AndAlso token.Parent.IsKind(SyntaxKind.Interpolation)) Then Return indenter.IndentFromStartOfLine(0) End If + If containingToken.Kind = SyntaxKind.StringLiteralToken AndAlso containingToken.FullSpan.Contains(position) Then Return indenter.IndentFromStartOfLine(0) End If diff --git a/src/Workspaces/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj b/src/Workspaces/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj index 2fc914af44c65..602bb056c19b5 100644 --- a/src/Workspaces/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj +++ b/src/Workspaces/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj @@ -5,7 +5,7 @@ Library netcoreapp3.1;netstandard2.0 - partial + partial true diff --git a/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb b/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb index 2ca6c7436ee81..6d52affd95f6f 100644 --- a/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb +++ b/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb @@ -211,6 +211,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Recommendations If container Is Nothing AndAlso TypeOf (leftHandTypeInfo.ConvertedType) Is IArrayTypeSymbol Then container = leftHandTypeInfo.ConvertedType End If + If container.IsErrorType() AndAlso leftHandSymbolInfo.Symbol IsNot Nothing Then ' TODO remove this when 531549 which causes leftHandTypeInfo to be an error type is fixed container = leftHandSymbolInfo.Symbol.GetSymbolType() @@ -276,12 +277,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Recommendations Debug.Assert(Not excludeInstance OrElse Not useBaseReferenceAccessibility) - If _context.TargetToken.GetPreviousToken().IsKind(SyntaxKind.QuestionToken) Then - Dim type = TryCast(container, INamedTypeSymbol) - If type?.ConstructedFrom.SpecialType = SpecialType.System_Nullable_T Then - container = type.GetTypeArguments().First() - End If - End If + ' On null conditional access, members of T for a Nullable(Of T) should be recommended + Dim unwrapNullable = _context.TargetToken.GetPreviousToken().IsKind(SyntaxKind.QuestionToken) ' No completion on types/namespace after conditional access If leftExpression.Parent.IsKind(SyntaxKind.ConditionalAccessExpression) AndAlso @@ -297,7 +294,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Recommendations .SelectMany(Function(n) LookupSymbolsInContainer(n, position, excludeInstance)) _ .ToImmutableArray() Else - symbols = GetMemberSymbols(container, position, excludeInstance, useBaseReferenceAccessibility) + symbols = GetMemberSymbols(container, position, excludeInstance, useBaseReferenceAccessibility, unwrapNullable) End If If excludeShared Then @@ -353,6 +350,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Recommendations ElseIf s.Kind = SymbolKind.NamedType AndAlso s.IsImplicitlyDeclared Then Return Not TypeOf DirectCast(s, INamedTypeSymbol).AssociatedSymbol Is IEventSymbol End If + Return True End Function diff --git a/src/Workspaces/VisualBasic/Portable/Rename/LocalConflictVisitor.vb b/src/Workspaces/VisualBasic/Portable/Rename/LocalConflictVisitor.vb index 09ab4fe62a505..d54f734454013 100644 --- a/src/Workspaces/VisualBasic/Portable/Rename/LocalConflictVisitor.vb +++ b/src/Workspaces/VisualBasic/Portable/Rename/LocalConflictVisitor.vb @@ -73,6 +73,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename For Each clause In node.Clauses Visit(clause) Next + _tracker.RemoveIdentifiers(tokens) End Sub @@ -94,6 +95,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename For Each statement In block Visit(statement) Next + _tracker.RemoveIdentifiers(tokens) End Sub diff --git a/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb b/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb index 99e3d9f333331..6939f97f48342 100644 --- a/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb +++ b/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb @@ -1033,6 +1033,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename Return Nothing End If End If + Dim isInNamespaceOrTypeContext = SyntaxFacts.IsInNamespaceOrTypeContext(TryCast(syntax, ExpressionSyntax)) Dim position = nodeToSpeculate.SpanStart Return SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(nodeToSpeculate, DirectCast(originalSemanticModel, SemanticModel), position, isInNamespaceOrTypeContext) diff --git a/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb b/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb index 7d57111fce0ca..e3689929d2b48 100644 --- a/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb +++ b/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb @@ -122,6 +122,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Serialization Dim value = reader.ReadValue() builder.Add(KeyValuePairUtil.Create(key, value)) Next + Dim options = New VisualBasicParseOptions(languageVersion, documentationMode, kind, builder.MoveToImmutable()) Return options.WithFeatures(features) End Function diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb index a04f9b6d0efd6..9e0183be63796 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb @@ -176,6 +176,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Return True End If End If + Return False End Function diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb b/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb index 0fb536a4880ed..1bdd54ae70f13 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb @@ -365,6 +365,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification ' No duplicate names allowed Return False End If + found = True End If Next diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.NodesAndTokensToReduceComputer.vb b/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.NodesAndTokensToReduceComputer.vb index 84204be78a458..92d5adb9d8e09 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.NodesAndTokensToReduceComputer.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.NodesAndTokensToReduceComputer.vb @@ -73,6 +73,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Me._simplifyAllDescendants Then Me._nodesAndTokensToReduce.Add(New NodeOrTokenToReduce(node, False, node, False)) End If + node = MyBase.Visit(node) End If @@ -127,6 +128,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Function(n, b, s, e) Return DirectCast(n, MethodBlockSyntax).Update(node.Kind, DirectCast(b, MethodStatementSyntax), s, e) End Function + Return VisitMethodBlockBaseSyntax(node, updateFunc) End Function @@ -135,6 +137,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Function(n, b, s, e) Return DirectCast(n, OperatorBlockSyntax).Update(DirectCast(b, OperatorStatementSyntax), s, e) End Function + Return VisitMethodBlockBaseSyntax(node, updateFunc) End Function @@ -143,6 +146,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Function(n, b, s, e) Return DirectCast(n, ConstructorBlockSyntax).Update(DirectCast(b, SubNewStatementSyntax), s, e) End Function + Return VisitMethodBlockBaseSyntax(node, updateFunc) End Function @@ -151,6 +155,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Function(n, b, s, e) Return DirectCast(n, AccessorBlockSyntax).Update(node.Kind, DirectCast(b, AccessorStatementSyntax), s, e) End Function + Return VisitMethodBlockBaseSyntax(node, updateFunc) End Function diff --git a/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb b/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb index 0a07e36f07f5e..d3038b363a9a3 100644 --- a/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb +++ b/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb @@ -51,6 +51,7 @@ End NameSpace" If symbol IsNot Nothing Then Return c.WithAdditionalAnnotations(SymbolAnnotation.Create(symbol), Simplifier.Annotation) End If + Return c End Function) doc = doc.WithSyntaxRoot(root) diff --git a/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb b/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb index e12dda6b0c2e9..d738e02bd7908 100644 --- a/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb +++ b/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb @@ -2553,6 +2553,18 @@ End Class Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.WithModifiers(SyntaxFactory.TypeParameter("tp"), DeclarationModifiers.Abstract))) End Sub + + Public Sub TestWithModifiers_Sealed() + Dim classBlock = DirectCast(Generator.ClassDeclaration("C"), ClassBlockSyntax) + Dim classBlockWithModifiers = Generator.WithModifiers(classBlock, DeclarationModifiers.Sealed) + VerifySyntax(Of ClassBlockSyntax)(classBlockWithModifiers, "NotInheritable Class C +End Class") + + Dim classStatement = classBlock.ClassStatement + Dim classStatementWithModifiers = Generator.WithModifiers(classStatement, DeclarationModifiers.Sealed) + VerifySyntax(Of ClassStatementSyntax)(classStatementWithModifiers, "NotInheritable Class C") + End Sub + Public Sub TestGetType() Assert.Equal("t", Generator.GetType(Generator.MethodDeclaration("m", returnType:=Generator.IdentifierName("t"))).ToString()) diff --git a/src/Workspaces/VisualBasicTest/VisualBasicExtensionsTests.vb b/src/Workspaces/VisualBasicTest/VisualBasicExtensionsTests.vb index 91f875cafeea6..97b144173b464 100644 --- a/src/Workspaces/VisualBasicTest/VisualBasicExtensionsTests.vb +++ b/src/Workspaces/VisualBasicTest/VisualBasicExtensionsTests.vb @@ -23,6 +23,7 @@ Module Module1 For i = 0 To 3000 code.Append("""asdf"" + ") Next + code.AppendLine(.Value)