diff --git a/azure-pipelines-integration-dartlab.yml b/azure-pipelines-integration-dartlab.yml index 4db72a49e343c..78389376fc4bb 100644 --- a/azure-pipelines-integration-dartlab.yml +++ b/azure-pipelines-integration-dartlab.yml @@ -68,4 +68,4 @@ stages: configuration: $(_configuration) oop64bit: $(_oop64bit) lspEditor: false - shallowCheckout: false + skipCheckout: true diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md index 85417b7843492..4f99b63cec964 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md @@ -1,5 +1,18 @@ # This document lists known breaking changes in Roslyn after .NET 6 all the way to .NET 7. +## Types cannot be named `file` + +***Introduced in Visual Studio 2022 version 17.4.*** Starting in C# 11, types cannot be named `file`. The compiler will report an error on all such type names. To work around this, the type name and all usages must be escaped with an `@`: + +```csharp +class file {} // Error CS9056 +class @file {} // No error +``` + +This was done as `file` is now a modifier for type declarations. + +You can learn more about this change in the associated [csharplang issue](https://github.com/dotnet/csharplang/issues/6011). + ## Required spaces in #line span directives ***Introduced in .NET SDK 6.0.400, Visual Studio 2022 version 17.3.*** @@ -426,4 +439,4 @@ class @required {} // No error This was done as `required` is now a member modifier for properties and fields. -You can learn more about this change in the associated [csharplang issue](https://github.com/dotnet/csharplang/issues/3630). +You can learn more about this change in the associated [csharplang issue](https://github.com/dotnet/csharplang/issues/3630). \ No newline at end of file diff --git a/docs/contributing/Compiler Test Plan.md b/docs/contributing/Compiler Test Plan.md index f287fc02e3f49..a3d0b6b4f463a 100644 --- a/docs/contributing/Compiler Test Plan.md +++ b/docs/contributing/Compiler Test Plan.md @@ -38,6 +38,7 @@ This document provides guidance for thinking about language interactions and tes # Type and members - Access modifiers (public, protected, internal, protected internal, private protected, private), static, ref - type declarations (class, record class/struct with or without positional members, struct, interface, type parameter) +- file-local types - methods - fields (required and not) - properties (including get/set/init accessors, required and not) diff --git a/docs/features/incremental-generators.md b/docs/features/incremental-generators.md index 5009d395ef7a5..c6b7923898494 100644 --- a/docs/features/incremental-generators.md +++ b/docs/features/incremental-generators.md @@ -89,7 +89,7 @@ single time for the lifetime of the host. Rather than a dedicated `Execute` method, an Incremental Generator instead defines an immutable execution pipeline as part of initialization. The `Initialize` method receives an instance of -`IncrementalGeneratorInitializationContext`which is used by the generator to +`IncrementalGeneratorInitializationContext` which is used by the generator to define a set of transformations. @@ -160,7 +160,7 @@ IValueProvider └─────────────┘ ``` -Instead, the generator supplies a set of transformations that ar to be applied to the +Instead, the generator supplies a set of transformations that are to be applied to the data contained within the provider, which in turn creates a new value provider. ### Select @@ -799,7 +799,7 @@ even though they referenced something in `file1.cs`. Because the first check is purely _syntactic_ we can be sure the results for `file2.cs` would be the same. While it may seem unfortunate that the driver must run the `transform` for all -selected syntax nodes, if it did not it could up producing incorrect data +selected syntax nodes, if it did not it could end up producing incorrect data due to cross file dependencies. Because the initial syntactic check allows the driver to substantially filter the number of nodes on which the semantic checks have to be re-run, significantly improved performance @@ -826,7 +826,7 @@ The set of output methods are **RegisterSourceOutput**: -RegisterSourceOutput allows a generator author to produce source files and +`RegisterSourceOutput` allows a generator author to produce source files and diagnostics that will be included in the users compilation. As input, it takes a `Value[s]Provider` and an `Action` that will be invoked for every value in the value provider. @@ -905,15 +905,15 @@ transformations are run, meaning that it will be visible as part of the rest of the regular execution pipeline, and an author may ask semantic questions about it. -It is particularly useful for adding attributes to the users source code. These -can then be added by the user their code, and the generator may find the -attributed code via the semantic model. +It is particularly useful for adding attribute definitions to the users' +source code. These can then be applied by the user in their code, and the +generator may find the attributed code via the semantic model. ## Handling Cancellation Incremental generators are designed to be used in interactive hosts such as an IDE. As such, it is critically important that generators respect and respond to -the passed in cancellation tokens. +the passed-in cancellation tokens. In general, it is likely that the amount of user computation performed per transformation is low, but often will be calling into Roslyn APIs that may have @@ -1033,7 +1033,7 @@ the driver knows there can be no work to be done when a `SyntaxTree` changes. ### Comparing Items -For a user provided result to be comparable across iterations, there needs to be +For a user-provided result to be comparable across iterations, there needs to be some concept of equivalence. By default the host will use `EqualityComparer` to determine equivalence. There are obviously times where this is insufficient, and there exists an extension method that allows the author to supply a comparer diff --git a/docs/ide/specs/semantic_snippets.md b/docs/ide/specs/semantic_snippets.md new file mode 100644 index 0000000000000..5cb4b2dabf1cf --- /dev/null +++ b/docs/ide/specs/semantic_snippets.md @@ -0,0 +1,465 @@ +# Semantic Snippets + +@jmarolf | Status: Draft + +Snippets as they exist today for C# developers only do text expansion (`cw -> Console.WriteLine`) and have lacked any significant improvements in the last 10+ years. We would like to iterate on this experience for .NET developers using Visual Studio. The hope is that typing (the thing developers do a lot) can also be the best way to discover how to and write code. + +## Table of Contents + +- [Requirements](#requirements) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Scenarios and User Experience](#scenarios-and-user-experience) + - [User Experience: Snippet commit behavior](#user-experience-snippet-commit-behavior) + - [User Experience: Snippet behavior with existing options](#user-experience-snippet-behavior-with-existing-options) + - [Scenario 1: Add New Snippets](#scenario-1-add-new-snippets) + - [record snippet](#record-snippet) + - [record struct snippet](#record-struct-snippet) + - [Scenario 2: Snippets only show up where they are valid](#scenario-2-snippets-only-show-up-where-they-are-valid) + - [Existing Snippet Valid Insertion Locations](#existing-snippet-valid-insertion-locations) + - [New Snippet Valid Insertion Locations](#new-snippet-valid-insertion-locations) + - [Scenario 3: Snippet text is relevant to my code](#scenario-3-snippet-text-is-relevant-to-my-code) + - [`Console.WriteLine`](#consolewriteline) + - [MessageBox.Show](#messageboxshow) + - [Invoke an Event](#invoke-an-event) + - [Scenario 3: Ability to populate snippets replacement parameters from context](#scenario-3-ability-to-populate-snippets-replacement-parameters-from-context) + - [Boolean snippets](#boolean-snippets) + - [Loop snippets](#loop-snippets) + - [Scenario 4: Snippets are discoverable](#scenario-4-snippets-are-discoverable) + - [Snippets Synonyms](#snippets-synonyms) +- [Design](#design) + - [Snippet Completion Provider](#snippet-completion-provider) + - [Custom Completion Sorting Logic](#custom-completion-sorting-logic) + - [Complex Edits](#complex-edits) + - [LSP Considerations](#lsp-considerations) +- [Test Plan](#test-plan) + - [Ensuring we don't break muscle memory](#ensuring-we-dont-break-muscle-memory) + - [Ensuring out new feautures work](#ensuring-out-new-feautures-work) +- [Q & A](#q--a) + +## Requirements + +### Goals + +- Snippet functionality of more discoverable for all users +- Snippets are more targeted to what a user is trying to do +- We do not want existing users to have their flow interrupted + +### Non-Goals + +- We are not making a general-purpose extension API for snippets (yet) + - We may do this someday but for now we want to ensure that we can meet customers’ needs for the narrow case +- We are not changing the VB experience for snippets (yet) + - VB has a very different design than C#. +- We are not changing "Surround With" behavior +- we are not changing snippet menu behavior (Ctrl+K,X) + +## Scenarios and User Experience + +### User Experience: Snippet commit behavior + +- We should change the commit behavior of these new snippets to only require a single tab to complete +- If the user types `Tab ↹` twice (due to muscle memory) we will eat the second key press (similar to how automatic brace matching works) +- Two undo stacks will be created for each snippet that is commited (`Console.WriteLine()` -> `cw` -> ``) + +### User Experience: Snippet behavior with existing options + +There is also the snippet options under `Tools->Options->Text Editor->C# Intellisense`: + +![image](https://user-images.githubusercontent.com/9797472/137983382-f243e241-fc4a-4529-be08-18e3ee7d5174.png) + +"Always include snippets" is the default and this is what over 90% of user have set. + +1. If "Always include snippets" is on + - We need to preserve the muscle memory of users with this option while being allowed to change the visual presentation. + - Even though “cw” won’t appear in the completion list with our feature turned on it still “needs to work” from the users perspective so the following experience needs to be preserved: + - User types `cw` + - Nothing is pre-selected in the completion list such that cw is over-written + - Tab-Tab executed the console.writeline(); snippet +1. If "Never include snippets" is on + - If the user has decided to never include snippets in the completion list then we also want to preserve this behavior + - User types `cw` + - The completion list pre-selects an item from the list + - Tab commits this item from the list to the editor +1. If "Include snippets when ?-Tab is typed after an identifier" is on + - Might want to consider removing this option as its rather odd and cannot find any data to suggests that people use this. If we decided to include this then the behavior is + - No ?-Tab: behaves like 2 above + - ?-Tab: behaves like 1 above + +### Scenario 1: Add New Snippets + +*We should offer new snippets for new language constructs.* + +|Name |Description | +|-----|------------| +|`record` | Creates a record declaration. | +|`srecord` | Creates a record struct declaration. | + +#### record snippet + +```csharp +record MyRecord() // users cursor is placed inside the parenthesis +{ +} +``` + +#### record struct snippet + +```csharp +record struct MyRecord() // users cursor is placed inside the parenthesis +{ +} +``` + +### Scenario 2: Snippets only show up where they are valid + +*If a user types an existing snippet in a location where it is not valid (example `cw`) nothing will be shown and no snippet suggestions will be given to the user.* + +Below is a list of snippets that the contexts in which they should appear + +#### Existing Snippet Valid Insertion Locations + +|Name | Valid locations to insert snippet| +|-----|----------------------------------| +|`#if` | Anywhere. +|`#region` | Anywhere. +|`~` | Inside a class or record. +|`attribute` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`checked` | Inside a method, an indexer, a property accessor, or an event accessor. +|`class` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`ctor` | Inside a class or record. +|`cw` | Inside a method, an indexer, a property accessor, or an event accessor. +|`do` | Inside a method, an indexer, a property accessor, or an event accessor. +|`else` | Inside a method, an indexer, a property accessor, or an event accessor. +|`enum` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`equals` | Inside a class, or a struct. +|`exception` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`for` | Inside a method, an indexer, a property accessor, or an event accessor. +|`foreach` | Inside a method, an indexer, a property accessor, or an event accessor. +|`forr` | Inside a method, an indexer, a property accessor, or an event accessor. +|`if` | Inside a method, an indexer, a property accessor, or an event accessor. +|`indexer` | Inside a class, recordstruct record, or a struct. +|`interface` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`invoke` | Inside a method, an indexer, a property accessor, or an event accessor. +|`iterator` | Inside a class, recordstruct record, or a struct. +|`iterindex` | Inside a class, recordstruct record, or a struct. +|`lock` | Inside a method, an indexer, a property accessor, or an event accessor. +|`mbox` | (If inside a winforms project) Inside a method, an indexer, a property accessor, or an event accessor. +|`namespace` | Inside a namespace (including the global namespace). +|`prop` | Inside a class, record ,or a struct. +|`propfull` | Inside a class, record, struct record, or a struct. +|`propg` | Inside a class, record, struct record, or a struct. +|`sim` | Inside a class, record, struct record, or a struct. +|`struct` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`svm` | Inside a class, record, struct record, or a struct. +|`switch` | Inside a method, an indexer, a property accessor, or an event accessor. +|`try` | Inside a method, an indexer, a property accessor, or an event accessor. +|`tryf` | Inside a method, an indexer, a property accessor, or an event accessor. +|`unchecked` | Inside a method, an indexer, a property accessor, or an event accessor. +|`unsafe` | Inside a method, an indexer, a property accessor, or an event accessor. +|`using` | Inside a namespace (including the global namespace). +|`while` | Inside a method, an indexer, a property accessor, or an event accessor. + +#### New Snippet Valid Insertion Locations + +|Name | Valid locations to insert snippet| +|-----|----------------------------------| +|`record` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`srecord` | Inside a namespace (including the global namespace), a class, record, struct record, or a struct. +|`elseif` | Inside a method, an indexer, a property accessor, or an event accessor. + +### Scenario 3: Snippet text is relevant to my code + +When possible, we want snippets to offer more fine-grained help to users writing code. There are two snippets where we want to change the code we complete dependeing on context. + +#### `Console.WriteLine` + +Today `cw` always expands to the text below: + +```csharp +Console.WriteLine(); // Users cursor is placed inside the parenthesis +``` + +This should remain the same for normal methods. + +```csharp +void M() +{ + Console.WriteLine(); // cursor is placed inside the parenthesis +} +``` + +however inside an `async` method we should complete to this instead + +```csharp +async Task M() +{ + await Console.Out.WriteLineAsync(); // cursor is placed inside the parenthesis +} +``` + +#### MessageBox.Show + +`mbox` always expands to the following text + +```csharp +System.Windows.Forms.MessageBox.Show("Test"); +``` + +This is a perfectly valid snippet but we only want it to show under the following circumstances: + +1. The user is in a .NET Framework project +1. The user is int a .NET Core project with `true` set + +#### Invoke an Event + +This snippet (`invoke`) was added to help with invoking events before the introduction of the `?.` operator. It always generates the following text: + +```csharp +[|EventHandler|] temp = [|MyEvent|]; // two tab stop groups denoted with [||] + // 1. first the user fills in the event type + // 2. then the user gives the identifier name of the event +if (temp != null) +{ + temp(); +} +``` + +If the user it targeting a version that is older than C# 6 (where `?.` was introduced) we should still generate something similar to what the original snippet did but with better help. + +```csharp +event System.Action MyEvent; + +void CallMyEvent() +{ + Action? temp = [|MyEvent|]; // Just one tab stop group where the user fills in the identifier of the event + // NOTE: we should also show a target-typed completion list here to help fill in the event + // Once the user types `Tab` to complete filling in the event identifier we will update the + // type of `temp` (if the user has non-var code style preferences). + if (temp != null) + { + temp([||]); // The second tab group is now here since most events require arguments, whole-method completion should be turned on for this line + } +} +``` + +If the user is doing this in a version of C# that is newer than version 5 things are significantly simpler. We will still offer a target-typed completion for events that are in scopt but we just generate this code: + +```csharp +event System.Action MyEvent; + +void CallMyEvent() +{ + MyEvent?.Invoke([||]); // The user was offered a list of event types in scope and upon selection the whole line was re-writted to this. + // As above, whole-line completion helps fill in the argument list here. +} +``` + +If the user selects an event that returns an awaitable type things get more complex. + +```csharp +event System.Func MyEvent; + +void CallMyEvent() +{ + Func? myEvent = [|MyEvent|]; // user chose an event that returns an awaitable type + if (myEvent != null) + { + Task eventTasks = Task.WhenAll(Array.ConvertAll( // we generate the code to ensure all the delegate results are awaited + myEvent.GetInvocationList(), + e => ((Func)e).Invoke([||]))); // As above, whole-line completion helps fill in the argument list here. + [|eventTasks.GetAwaiter().GetResult();|] // since the method is not async we offer a third group with the options + // 1. make method async + // 2. wait for all delegates to finish + } +} +``` + +If the user is inside of an `async` method _and_ they select an event that returns an awaitable we elide the third tab group. + +```csharp +event System.Func MyEvent; + +async Task CallMyEventAsync() +{ + Func? myEvent = [|MyEvent|]; // user chose an event that returns an awaitable type + if (myEvent != null) + { + await Task.WhenAll(Array.ConvertAll( + myEvent.GetInvocationList(), + e => ((Func)e).Invoke([||]))); // As above, whole-line completion helps fill in the argument list here. + } +} +``` + +### Scenario 3: Ability to populate snippets replacement parameters from context + +Note that is a snippet isn't called out here the expectation is that its behavior remanes the same. Meaning snippets like `tryf` expand to the same text. + +#### Boolean snippets + +- `do` +- `if` +- `while` +- `else` +- `elseif` + +These snippets create a block and have a place to but a boolean expression. The only additional work we want to do here is ensure the users are shown a target-typed list of members that are in scope + +Note that `else if` isn't a snippet today and in the past was handled by the user typing `else` Tab `if` Tab-Tab. Since we are changine this behavior a bit we should add an `elseif` snippet that completes the following + +```csharp +else if ([|true|]) // user is shown a target-typed list for bool members +{ + +} +``` + +#### Loop snippets + +- `for` +- `forr` +- `foreach` + +this is what `for` produces today: + +```csharp +for (int [|i|] = 0; i < [|length|]; i++) // user need to iterate through naming `i` then selecting what `length` should be +{ + +} +``` + +We can improve the process for filling in for loops here + +```csharp +for (int [|i|] = 0; i < [|length|]; i++) // we automatically choose a name for `i` so it does not conflict, + // show a list of in-scope variabled that have a Count or Length property and are indexable +{ + T [|temp|] = arrag[i]; // Once the uder commits the indexable member we generate a temp and let them name it. + // if the user is doing something that does not require reading from the indexable member esc undoes this +} +``` + +Today users are asked to fill in information on a foreach loop in the exact opposite order of what it should be: + +```csharp +foreach ([|var|] [|item|] in [|collection|]) // user needs to select `var` then `item` then `collection` +{ + +} +``` + +For foreach loops we will reverse the tab group ordering asking them to select the type the member they are iterating over first + +```csharp +foreach (var [|item|] in [|collection|]) // user selects`collection` first from list of IEnumerable variables then everything else is filled in based on preferenced. +{ // the user is allowed to override the chosen name by tabbing + // If the user preferes var then var is used for the variable type otherwise it is replaced with concrete type from the enumerable +} +``` + +By changing this order it also allows us to automatically deconstruct elements we are iterating over. + +```csharp +List<(int id, string name)> _students = new(); + +foreach ((int [|id|], string [|name|]) in [|_students|}) // user chose a type that can be decontructed so we do that automatically +{ // user can tab though the deconstructed names and change them if they want + +} +``` + +### Scenario 4: Snippets are discoverable + +Just typing what the user is trying to do snippets should show up and be obvious what they do. What also want to handle synonmyms. Below is a list of synonyms that should also filter to the snippet in the completion list. Note that the old trigger text for these snippets is included to ensure that typing the same keystrokes produces the same results. + +#### Snippets Synonyms + +|Name |Descriptive Text in Completion List | Valid synonyms | +|-----|------------|----------------------------------| +|`#if` | Create a #if directive and a #endif directive. | directive, pound, hashtag, #if +|`#region` | Create a #region directive and a #endregion directive. | region, pound, hashtag, #region +|`~` | Create a finalizer (destructor) for the containing class. | destruct, final, dispose, ~ +|`attribute` | Create a declaration for a class that derives from Attribute. | +|`checked` | Create a checked block. | +|`class` | Create a class declaration. | type, new +|`ctor` | Create a constructor for the containing class. | new +|`cw` | Write to the console. | write line, output, log, print, cw +|`do` | Create a do while loop. | +|`else` | Create an else block. | +|`enum` | Create an enum declaration. | new +|`equals` | Create a method declaration that overrides the Equals method defined in the Object class. | compare, equal, equals +|`exception` | Create a declaration for a class that derives from an exception (Exception by default). | +|`for` | Create a for loop. | +|`foreach` | Create a foreach loop. | +|`forr` | Create a for loop that decrements the loop variable after each iteration.| reverse, forr +|`if` | Create an if block. | +|`indexer` | Create an indexer declaration. | +|`interface` | Create an interface declaration. | +|`invoke` | Create a block that safely invokes an event. | event, invoke +|`iterator` | Create an iterator. | +|`iterindex` | Create a "named" iterator and indexer pair by using a nested class. | iterindex +|`lock` | Create a lock block. | +|`mbox` | Call System.Windows.Forms.MessageBox.Show. | popup, show, mbox +|`namespace` | Create a namespace declaration. | +|`prop` | Create an auto-implemented property declaration. | auto, prop +|`propfull` | Create a property declaration with get and set accessors. | full, propfull +|`propg` | Create a read-only auto-implemented property with a private set accessor. | propg +|`sim` | Create a static int Main method declaration | sim, entry, start +|`struct` | Create a struct declaration. | +|`svm` | Create a static void Main method declaration. | svm, entry, start +|`switch` | Create a switch block. | +|`try` | Create a try-catch block. | catch, handle +|`tryf` | Create a try-finally block. | finally, tryf +|`unchecked` | Create an unchecked block | +|`unsafe` | Create an unsafe block. | +|`using` | Create a using directive. | dispose +|`while` | Create a while loop. | +|`record` | Create a record declaration. | data, new +|`srecord` | Create a struct record declaration. | data, new + +## Design + +Essentially all of these new snippets can just be completion providers as the apis we have today should be enought to accmplish everything that is detailed here. There are a few adjusts that will need to be made to exising services to ensure these all fit in cleanly. + +### Snippet Completion Provider + +Work will need to be done [here](https://github.com/dotnet/roslyn/blob/1667bc61ac41e5e63ea672c2a39c1a0bebe5f8e9/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs#L146) and potentially [here](https://github.com/dotnet/roslyn/blob/main/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetInfoService.cs) to filter out _only_ the snippets that out team owns so we do not break third-party snippets. + +### Custom Completion Sorting Logic + +We may need to adjust the item sorting logic [here](https://github.com/dotnet/roslyn/blob/1667bc61ac41e5e63ea672c2a39c1a0bebe5f8e9/src/Features/Core/Portable/Completion/CompletionService.cs#L202) considering the synonym matching that we want to do. + +### Complex Edits + +Serveral of these complation behaviors will be complex edits to the docuement. Based on the existing API shape this should not be a problem but we may want to ensure that snippets (that the user generally thinks of a lightweight operation) do not slow things down by doing _overly_ complex things. + +### LSP Considerations + +The expectation is that LSP clients should be able to use these completion providers without issue but we should keep in contact with those teams to ensure this remains the case. + +## Test Plan + +### Ensuring we don't break muscle memory + +- Type in the following locations and ensure the same text is committed given the same keystroked + - inside namespace + - `cla⇥⇥Hi` -> `class Hi { }` + - `us⇥Sys⇥;` -> `using System;` + - inside class + - `svm⇥⇥` -> `static void Main(string[] args) { }` + - `prop⇥⇥` -> `public int MyProperty { get; set; }` + - `over⇥↓⇥` -> `public override bool Equals(object? obj) { return base.Equals(obj); }` + - `ev⇥Act⇥?MyEvent;` -> `event Action MyEvent;` + - inside method body, lambda, property, or local function + - `if⇥⇥` -> `if (true) { }` + - `forr⇥⇥` -> `for (int i = length - 1; i >= 0; i--) { }` + - `str⇥name = "Some Name;` -> `string name = "Some Name";` + - inside parameter list + - `str⇥name = "Some Name` -> `string name = "Some Name"` + +## Q & A + +- [C# snippet Docs](https://docs.microsoft.com/visualstudio/ide/visual-csharp-code-snippets) +- [Visual Studio Snippet XML Schema](https://docs.microsoft.com//visualstudio/ide/code-snippets-schema-reference) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e8c8583fab4d9..e5e0d75b2b692 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,18 +13,18 @@ - + https://github.com/dotnet/arcade - a264eb13fea14125f3ef8d4056586cd66fa55309 + 9f45238c23d89cf44a10705db1217e66a441ba5a https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - a264eb13fea14125f3ef8d4056586cd66fa55309 + 9f45238c23d89cf44a10705db1217e66a441ba5a diff --git a/eng/Versions.props b/eng/Versions.props index 2fbb786fc4d1a..346bfebb5811c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,9 +6,9 @@ --> 4 - 3 + 4 0 - 3 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) - 6.3.0-preview.1.32 + 6.3.0-preview.2.73 $(NuGetCommonVersion) $(NuGetCommonVersion) $(NuGetCommonVersion) @@ -262,9 +262,9 @@ 1.3.2 2.1.26 $(xunitVersion) - 2.4.1 + 2.4.5 1.0.51 - $(xunitVersion) + 2.4.5 $(xunitVersion) $(ILAsmPackageVersion) $(ILAsmPackageVersion) diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index 24a5e65de1b3e..8d48ec5680fc4 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -87,6 +87,7 @@ try { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value + $InstalledTools = @{} if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { if ($ToolVersion -eq "latest") { @@ -111,9 +112,10 @@ try { $ToolPath = Convert-Path -Path $BinPath Write-Host "Adding $ToolName to the path ($ToolPath)..." Write-Host "##vso[task.prependpath]$ToolPath" + $InstalledTools += @{ $ToolName = $ToolDirectory.FullName } } } - exit 0 + return $InstalledTools } else { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index 6d7ba15e5f2b5..4b99a9cad3b77 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -71,7 +71,7 @@ if [[ -z "$CLR_CC" ]]; then # Set default versions if [[ -z "$majorVersion" ]]; then # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [[ "$compiler" == "clang" ]]; then versions=( 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) + if [[ "$compiler" == "clang" ]]; then versions=( 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) elif [[ "$compiler" == "gcc" ]]; then versions=( 12 11 10 9 8 7 6 5 4.9 ); fi for version in "${versions[@]}"; do diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 395b43eebb662..9638c63c7258b 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -635,7 +635,7 @@ function InitializeNativeTools() { InstallDirectory = "$ToolsDir" } } - if (Test-Path variable:NativeToolsOnMachine) { + if ($env:NativeToolsOnMachine) { Write-Host "Variable NativeToolsOnMachine detected, enabling native tool path promotion..." $nativeArgs += @{ PathPromotion = $true } } diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index e2ecb671d676d..b7f62ee93d3ec 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -106,14 +106,14 @@ "vsMajorVersion": 17, "insertionTitlePrefix": "[d17.2]" }, - "release/dev17.3": { + "release/dev17.3-vs-deps": { "nugetKind": [ "Shipping", "NonShipping" ], - "vsBranch": "rel/d17.3", + "vsBranch": "main", "vsMajorVersion": 17, - "insertionTitlePrefix": "[d17.3p2]" + "insertionTitlePrefix": "[d17.3p3]" }, "main": { "nugetKind": [ @@ -132,17 +132,7 @@ ], "vsBranch": "main", "vsMajorVersion": 17, - "insertionCreateDraftPR": true, - "insertionTitlePrefix": "[d17.3p3]" - }, - "release/dev17.4-vs-deps": { - "nugetKind": [ - "Shipping", - "NonShipping" - ], - "vsBranch": "main", - "vsMajorVersion": 17, - "insertionCreateDraftPR": true, + "insertionCreateDraftPR": false, "insertionTitlePrefix": "[d17.4p1]" } } diff --git a/eng/pipelines/checkout-unix-task.yml b/eng/pipelines/checkout-unix-task.yml index 5a6a75335c610..e65175caf5a7d 100644 --- a/eng/pipelines/checkout-unix-task.yml +++ b/eng/pipelines/checkout-unix-task.yml @@ -1,11 +1,14 @@ # Shallow checkout sources on Unix + steps: - checkout: none - script: | set -x git init + git config --local checkout.workers 0 + git config --local fetch.parallel 0 git remote add origin "$(Build.Repository.Uri)" - git fetch --progress --no-tags --depth=1 origin "$(Build.SourceVersion)" + git fetch --no-tags --no-auto-maintenance --depth=1 origin "$(Build.SourceVersion)" git checkout "$(Build.SourceVersion)" - displayName: Shallow Checkout \ No newline at end of file + displayName: Shallow Checkout diff --git a/eng/pipelines/checkout-windows-task.yml b/eng/pipelines/checkout-windows-task.yml index b506b0d888e53..304aa7868a5f9 100644 --- a/eng/pipelines/checkout-windows-task.yml +++ b/eng/pipelines/checkout-windows-task.yml @@ -5,10 +5,12 @@ steps: - checkout: none - script: | - @echo on set PATH=$(Agent.HomeDirectory)\externals\git\cmd;%PATH% + @echo on git init + git config --local checkout.workers 0 + git config --local fetch.parallel 0 git remote add origin "$(Build.Repository.Uri)" - git fetch --progress --no-tags --depth=1 origin "$(Build.SourceVersion)" + git fetch --no-tags --no-auto-maintenance --depth=1 origin "$(Build.SourceVersion)" git checkout "$(Build.SourceVersion)" displayName: Shallow Checkout diff --git a/eng/pipelines/test-integration-job.yml b/eng/pipelines/test-integration-job.yml index 2f1156bbc8007..47804a6d9fae4 100644 --- a/eng/pipelines/test-integration-job.yml +++ b/eng/pipelines/test-integration-job.yml @@ -14,12 +14,13 @@ parameters: - name: lspEditor type: string default: false - - name: shallowCheckout + - name: skipCheckout type: boolean - default: true + default: false steps: - - ${{ if eq(parameters.shallowCheckout, true) }}: + # Pipelines like the DartLab integration pipeline skip checkout because it is performed elsewhere. + - ${{ if eq(parameters.skipCheckout, false) }}: - template: checkout-windows-task.yml - task: PowerShell@2 diff --git a/global.json b/global.json index c21c2c7dce240..36228228beb42 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,11 @@ { "sdk": { - "version": "7.0.100-preview.4.22252.9", + "version": "7.0.100-preview.5.22307.18", "allowPrerelease": true, "rollForward": "latestPatch" }, "tools": { - "dotnet": "7.0.100-preview.4.22252.9", + "dotnet": "7.0.100-preview.5.22307.18", "runtimes": { "dotnet": [ "3.1.0" @@ -17,7 +17,7 @@ "xcopy-msbuild": "17.1.0" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22327.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22327.2" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22355.4", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22355.4" } -} \ No newline at end of file +} diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnreachableCode/RemoveUnreachableCodeHelpers.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnreachableCode/RemoveUnreachableCodeHelpers.cs index 8fd9f8264bd89..6fc032eac1813 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnreachableCode/RemoveUnreachableCodeHelpers.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnreachableCode/RemoveUnreachableCodeHelpers.cs @@ -2,7 +2,9 @@ // 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.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; @@ -12,15 +14,40 @@ internal static class RemoveUnreachableCodeHelpers { public static ImmutableArray> GetSubsequentUnreachableSections(StatementSyntax firstUnreachableStatement) { - SyntaxList siblingStatements; + ImmutableArray siblingStatements; switch (firstUnreachableStatement.Parent) { case BlockSyntax block: - siblingStatements = block.Statements; + siblingStatements = ImmutableArray.CreateRange(block.Statements); break; case SwitchSectionSyntax switchSection: - siblingStatements = switchSection.Statements; + siblingStatements = ImmutableArray.CreateRange(switchSection.Statements); + break; + + case GlobalStatementSyntax globalStatement: + if (globalStatement.Parent is not CompilationUnitSyntax compilationUnit) + { + return ImmutableArray>.Empty; + } + + { + // Can't use `SyntaxList` here since the retrieving the added node will result + // in a different reference. + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var member in compilationUnit.Members) + { + if (member is not GlobalStatementSyntax currentGlobalStatement) + { + continue; + } + + builder.Add(currentGlobalStatement.Statement); + } + + siblingStatements = builder.ToImmutable(); + } + break; default: @@ -33,7 +60,7 @@ public static ImmutableArray> GetSubsequentUnrea var currentSection = ArrayBuilder.GetInstance(); var firstUnreachableStatementIndex = siblingStatements.IndexOf(firstUnreachableStatement); - for (int i = firstUnreachableStatementIndex + 1, n = siblingStatements.Count; i < n; i++) + for (int i = firstUnreachableStatementIndex + 1, n = siblingStatements.Length; i < n; i++) { var currentStatement = siblingStatements[i]; if (currentStatement.IsKind(SyntaxKind.LabeledStatement)) diff --git a/src/Analyzers/CSharp/Analyzers/UseNullPropagation/CSharpUseNullPropagationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseNullPropagation/CSharpUseNullPropagationDiagnosticAnalyzer.cs index d6a8edf851e20..d4c1b966a053f 100644 --- a/src/Analyzers/CSharp/Analyzers/UseNullPropagation/CSharpUseNullPropagationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseNullPropagation/CSharpUseNullPropagationDiagnosticAnalyzer.cs @@ -19,13 +19,17 @@ internal class CSharpUseNullPropagationDiagnosticAnalyzer : AbstractUseNullPropagationDiagnosticAnalyzer< SyntaxKind, ExpressionSyntax, + StatementSyntax, ConditionalExpressionSyntax, BinaryExpressionSyntax, InvocationExpressionSyntax, - MemberAccessExpressionSyntax, ConditionalAccessExpressionSyntax, - ElementAccessExpressionSyntax> + ElementAccessExpressionSyntax, + IfStatementSyntax, + ExpressionStatementSyntax> { + protected override SyntaxKind IfStatementSyntaxKind => SyntaxKind.IfStatement; + protected override bool ShouldAnalyze(Compilation compilation) => compilation.LanguageVersion() >= LanguageVersion.CSharp6; @@ -36,29 +40,66 @@ protected override bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNo => node.IsInExpressionTree(semanticModel, expressionTypeOpt, cancellationToken); protected override bool TryAnalyzePatternCondition( - ISyntaxFacts syntaxFacts, SyntaxNode conditionNode, - [NotNullWhen(true)] out SyntaxNode? conditionPartToCheck, out bool isEquals) + ISyntaxFacts syntaxFacts, ExpressionSyntax conditionNode, + [NotNullWhen(true)] out ExpressionSyntax? conditionPartToCheck, out bool isEquals) { conditionPartToCheck = null; isEquals = true; if (conditionNode is not IsPatternExpressionSyntax patternExpression) - { return false; - } - if (patternExpression.Pattern is not ConstantPatternSyntax constantPattern) + var pattern = patternExpression.Pattern; + if (pattern is UnaryPatternSyntax(SyntaxKind.NotPattern) notPattern) { - return false; + isEquals = false; + pattern = notPattern.Pattern; } + if (pattern is not ConstantPatternSyntax constantPattern) + return false; + if (!syntaxFacts.IsNullLiteralExpression(constantPattern.Expression)) - { return false; - } conditionPartToCheck = patternExpression.Expression; return true; } + + protected override bool TryGetPartsOfIfStatement( + IfStatementSyntax ifStatement, + [NotNullWhen(true)] out ExpressionSyntax? condition, + [NotNullWhen(true)] out StatementSyntax? trueStatement) + { + // has to be of the form: + // + // if (...) + // statement + // + // or + // + // if (...) + // { + // statement + // } + + condition = ifStatement.Condition; + + trueStatement = null; + if (ifStatement.Else == null) + { + if (ifStatement.Statement is BlockSyntax block) + { + if (block.Statements.Count == 1) + trueStatement = block.Statements[0]; + } + else + { + trueStatement = ifStatement.Statement; + } + } + + return trueStatement != null; + } } } diff --git a/src/Analyzers/CSharp/Analyzers/UseUtf8StringLiteral/UseUtf8StringLiteralDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseUtf8StringLiteral/UseUtf8StringLiteralDiagnosticAnalyzer.cs index f97b4fcfb5a50..a0cbfc5874d40 100644 --- a/src/Analyzers/CSharp/Analyzers/UseUtf8StringLiteral/UseUtf8StringLiteralDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseUtf8StringLiteral/UseUtf8StringLiteralDiagnosticAnalyzer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Globalization; using System.Linq; using System.Text; using Microsoft.CodeAnalysis.CodeStyle; @@ -152,9 +153,12 @@ internal static bool TryConvertToUtf8String(StringBuilder? builder, ImmutableArr for (var i = 0; i < arrayCreationElements.Length;) { // Need to call a method to do the actual rune decoding as it uses stackalloc, and stackalloc - // in a loop is a bad idea - if (!TryGetNextRune(arrayCreationElements, i, out var rune, out var bytesConsumed)) + // in a loop is a bad idea. We also exclude any characters that are control or format chars + if (!TryGetNextRune(arrayCreationElements, i, out var rune, out var bytesConsumed) || + IsControlOrFormatRune(rune)) + { return false; + } i += bytesConsumed; @@ -173,6 +177,18 @@ internal static bool TryConvertToUtf8String(StringBuilder? builder, ImmutableArr } return true; + + // We allow the three control characters that users are familiar with and wouldn't be surprised to + // see in a string literal + static bool IsControlOrFormatRune(Rune rune) + => Rune.GetUnicodeCategory(rune) is UnicodeCategory.Control or UnicodeCategory.Format + && rune.Value switch + { + '\r' => false, + '\n' => false, + '\t' => false, + _ => true + }; } private static bool TryGetNextRune(ImmutableArray arrayCreationElements, int startIndex, out Rune rune, out int bytesConsumed) diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs b/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs index 779bc531903e5..c2d9fbfb02d40 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs @@ -39,69 +39,93 @@ public static async Task ConvertAsync(Document document, BaseNamespace return await ConvertFileScopedNamespaceAsync(document, fileScopedNamespace, cancellationToken).ConfigureAwait(false); case NamespaceDeclarationSyntax namespaceDeclaration: - var (doc, _) = await ConvertNamespaceDeclarationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false); - return doc; + return await ConvertNamespaceDeclarationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false); default: throw ExceptionUtilities.UnexpectedValue(baseNamespace.Kind()); } } - public static async Task<(Document document, TextSpan semicolonSpan)> ConvertNamespaceDeclarationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken) + /// + /// Asynchrounous implementation for code fixes. + /// + public static async ValueTask ConvertNamespaceDeclarationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken) { - // First, determine how much indentation we had inside the original block namespace. We'll attempt to remove + var parsedDocument = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + // Replace the block namespace with the file scoped namespace. + var annotation = new SyntaxAnnotation(); + var (updatedRoot, _) = ReplaceWithFileScopedNamespace(parsedDocument, namespaceDeclaration, annotation); + var updatedDocument = document.WithSyntaxRoot(updatedRoot); + + // Determine how much indentation we had inside the original block namespace. We'll attempt to remove // that much indentation from each applicable line after we conver the block namespace to a file scoped // namespace. + var indentation = GetIndentation(parsedDocument, namespaceDeclaration, options, cancellationToken); + if (indentation == null) + return updatedDocument; - var indentation = await GetIndentationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false); + // Now, find the file scoped namespace in the updated doc and go and dedent every line if applicable. + var updatedParsedDocument = await ParsedDocument.CreateAsync(updatedDocument, cancellationToken).ConfigureAwait(false); + var (dedentedText, _) = DedentNamespace(updatedParsedDocument, indentation, annotation, cancellationToken); + return document.WithText(dedentedText); + } - // Next, actually replace the block namespace with the file scoped namespace. + /// + /// Synchronous implementation for a command handler. + /// + public static (SourceText text, TextSpan semicolonSpan) ConvertNamespaceDeclaration(ParsedDocument document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken) + { + // Replace the block namespace with the file scoped namespace. var annotation = new SyntaxAnnotation(); - var (updatedDocument, semicolonSpan) = await ReplaceWithFileScopedNamespaceAsync(document, namespaceDeclaration, annotation, cancellationToken).ConfigureAwait(false); + var (updatedRoot, semicolonSpan) = ReplaceWithFileScopedNamespace(document, namespaceDeclaration, annotation); + var updatedDocument = document.WithChangedRoot(updatedRoot, cancellationToken); - // Now, find the file scoped namespace in the updated doc and go and dedent every line if applicable. + // Determine how much indentation we had inside the original block namespace. We'll attempt to remove + // that much indentation from each applicable line after we conver the block namespace to a file scoped + // namespace. + + var indentation = GetIndentation(document, namespaceDeclaration, options, cancellationToken); if (indentation == null) - return (updatedDocument, semicolonSpan); + return (updatedDocument.Text, semicolonSpan); - return await DedentNamespaceAsync(updatedDocument, indentation, annotation, cancellationToken).ConfigureAwait(false); + // Now, find the file scoped namespace in the updated doc and go and dedent every line if applicable. + return DedentNamespace(updatedDocument, indentation, annotation, cancellationToken); } - private static async Task<(Document document, TextSpan semicolonSpan)> ReplaceWithFileScopedNamespaceAsync( - Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxAnnotation annotation, CancellationToken cancellationToken) + private static (SyntaxNode root, TextSpan semicolonSpan) ReplaceWithFileScopedNamespace( + ParsedDocument document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxAnnotation annotation) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var converted = ConvertNamespaceDeclaration(namespaceDeclaration); - var updatedRoot = root.ReplaceNode( + var updatedRoot = document.Root.ReplaceNode( namespaceDeclaration, converted.WithAdditionalAnnotations(annotation)); var fileScopedNamespace = (FileScopedNamespaceDeclarationSyntax)updatedRoot.GetAnnotatedNodes(annotation).Single(); - return (document.WithSyntaxRoot(updatedRoot), fileScopedNamespace.SemicolonToken.Span); + return (updatedRoot, fileScopedNamespace.SemicolonToken.Span); } - private static async Task GetIndentationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken) + private static string? GetIndentation(ParsedDocument document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken) { - var indentationService = document.GetRequiredLanguageService(); - var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - - var openBraceLine = sourceText.Lines.GetLineFromPosition(namespaceDeclaration.OpenBraceToken.SpanStart).LineNumber; - var closeBraceLine = sourceText.Lines.GetLineFromPosition(namespaceDeclaration.CloseBraceToken.SpanStart).LineNumber; + var openBraceLine = document.Text.Lines.GetLineFromPosition(namespaceDeclaration.OpenBraceToken.SpanStart).LineNumber; + var closeBraceLine = document.Text.Lines.GetLineFromPosition(namespaceDeclaration.CloseBraceToken.SpanStart).LineNumber; if (openBraceLine == closeBraceLine) return null; // Auto-formatting options are not relevant since they only control behavior on typing. var indentationOptions = new IndentationOptions(options); + var indentationService = document.LanguageServices.GetRequiredService(); var indentation = indentationService.GetIndentation(document, openBraceLine + 1, indentationOptions, cancellationToken); - return indentation.GetIndentationString(sourceText, options.UseTabs, options.TabSize); + return indentation.GetIndentationString(document.Text, options.UseTabs, options.TabSize); } - private static async Task<(Document document, TextSpan semicolonSpan)> DedentNamespaceAsync( - Document document, string indentation, SyntaxAnnotation annotation, CancellationToken cancellationToken) + private static (SourceText text, TextSpan semicolonSpan) DedentNamespace( + ParsedDocument document, string indentation, SyntaxAnnotation annotation, CancellationToken cancellationToken) { - var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var text = await root.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); + var syntaxTree = document.SyntaxTree; + var text = document.Text; + var root = document.Root; var fileScopedNamespace = (FileScopedNamespaceDeclarationSyntax)root.GetAnnotatedNodes(annotation).Single(); var semicolonLine = text.Lines.GetLineFromPosition(fileScopedNamespace.SemicolonToken.SpanStart).LineNumber; @@ -111,7 +135,7 @@ public static async Task ConvertAsync(Document document, BaseNamespace changes.AddIfNotNull(TryDedentLine(syntaxTree, text, indentation, text.Lines[line], cancellationToken)); var dedentedText = text.WithChanges(changes); - return (document.WithText(dedentedText), fileScopedNamespace.SemicolonToken.Span); + return (dedentedText, fileScopedNamespace.SemicolonToken.Span); } private static TextChange? TryDedentLine( diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnreachableCode/CSharpRemoveUnreachableCodeCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnreachableCode/CSharpRemoveUnreachableCodeCodeFixProvider.cs index edcfa85ec0bc9..1d74eb4187264 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveUnreachableCode/CSharpRemoveUnreachableCodeCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnreachableCode/CSharpRemoveUnreachableCodeCodeFixProvider.cs @@ -2,13 +2,9 @@ // 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; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -17,15 +13,16 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.RemoveUnreachableCode { [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnreachableCode), Shared] - internal class CSharpRemoveUnreachableCodeCodeFixProvider : SyntaxEditorBasedCodeFixProvider + internal sealed class CSharpRemoveUnreachableCodeCodeFixProvider : SyntaxEditorBasedCodeFixProvider { [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpRemoveUnreachableCodeCodeFixProvider() { } @@ -64,8 +61,8 @@ protected override Task FixAllAsync( { foreach (var diagnostic in diagnostics) { - var firstUnreachableStatementLocation = diagnostic.AdditionalLocations.First(); - var firstUnreachableStatement = (StatementSyntax)firstUnreachableStatementLocation.FindNode(cancellationToken); + var firstUnreachableStatementLocation = diagnostic.AdditionalLocations[0]; + var firstUnreachableStatement = (StatementSyntax)firstUnreachableStatementLocation.FindNode(getInnermostNodeForTie: true, cancellationToken); RemoveStatement(editor, firstUnreachableStatement); @@ -85,7 +82,8 @@ protected override Task FixAllAsync( static void RemoveStatement(SyntaxEditor editor, SyntaxNode statement) { if (!statement.IsParentKind(SyntaxKind.Block) - && !statement.IsParentKind(SyntaxKind.SwitchSection)) + && !statement.IsParentKind(SyntaxKind.SwitchSection) + && !statement.IsParentKind(SyntaxKind.GlobalStatement)) { editor.ReplaceNode(statement, SyntaxFactory.Block()); } diff --git a/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs index 0b5d5e2b1ba73..46680895b8ed7 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs @@ -6,14 +6,17 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -63,7 +66,27 @@ protected override async Task FixAllAsync( assignment.Left, SyntaxFactory.Token(SyntaxKind.QuestionQuestionEqualsToken).WithTriviaFrom(assignment.OperatorToken), assignment.Right).WithTriviaFrom(assignment); - var newWhenTrueStatement = whenTrueStatement.ReplaceNode(assignment, newAssignment).WithTriviaFrom(ifStatement); + + var newWhenTrueStatement = whenTrueStatement.ReplaceNode(assignment, newAssignment); + + // If there's leading trivia on the original inner statement, then combine that with the leading + // trivia on the if-statement. We'll need to add a formatting annotation so that the leading comments + // are put in the right location. + if (newWhenTrueStatement.GetLeadingTrivia().Any(t => t.IsSingleOrMultiLineComment())) + { + newWhenTrueStatement = newWhenTrueStatement + .WithPrependedLeadingTrivia(ifStatement.GetLeadingTrivia()) + .WithAdditionalAnnotations(Formatter.Annotation); + } + else + { + newWhenTrueStatement = newWhenTrueStatement.WithLeadingTrivia(ifStatement.GetLeadingTrivia()); + } + + // If there's trailing comments on the original inner statement, then preserve that. Otherwise, + // replace it with the trailing trivia of hte original if-statement. + if (!newWhenTrueStatement.GetTrailingTrivia().Any(t => t.IsSingleOrMultiLineComment())) + newWhenTrueStatement = newWhenTrueStatement.WithTrailingTrivia(ifStatement.GetTrailingTrivia()); editor.ReplaceNode(ifStatement, newWhenTrueStatement); } diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index fd7d5962ff5ae..079674a8fdbb2 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -12,16 +12,18 @@ namespace Microsoft.CodeAnalysis.CSharp.UseNullPropagation { [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseNullPropagation), Shared] internal class CSharpUseNullPropagationCodeFixProvider : AbstractUseNullPropagationCodeFixProvider< - SyntaxKind, - ExpressionSyntax, - ConditionalExpressionSyntax, - BinaryExpressionSyntax, - InvocationExpressionSyntax, - MemberAccessExpressionSyntax, - ConditionalAccessExpressionSyntax, - ElementAccessExpressionSyntax, - ElementBindingExpressionSyntax, - BracketedArgumentListSyntax> + SyntaxKind, + ExpressionSyntax, + StatementSyntax, + ConditionalExpressionSyntax, + BinaryExpressionSyntax, + InvocationExpressionSyntax, + ConditionalAccessExpressionSyntax, + ElementAccessExpressionSyntax, + ElementBindingExpressionSyntax, + IfStatementSyntax, + ExpressionStatementSyntax, + BracketedArgumentListSyntax> { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] diff --git a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs index a944f772a8804..f940971e4d7f5 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs @@ -166,7 +166,7 @@ private static LocalDeclarationStatementSyntax Convert(UsingStatementSyntax usin { return LocalDeclarationStatement( usingStatement.AwaitKeyword, - usingStatement.UsingKeyword, + usingStatement.UsingKeyword.WithAppendedTrailingTrivia(ElasticMarker), modifiers: default, usingStatement.Declaration, Token(SyntaxKind.SemicolonToken)).WithTrailingTrivia(usingStatement.CloseParenToken.TrailingTrivia); diff --git a/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs index 31558b6555f3e..2e38282fe2e56 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs @@ -48,12 +48,26 @@ protected override async Task FixAllAsync( { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var readOnlySpanType = semanticModel.Compilation.GetBestTypeByMetadataName(typeof(ReadOnlySpan<>).FullName!); + // The analyzer wouldn't raise a diagnostic if this were null + Contract.ThrowIfNull(readOnlySpanType); + foreach (var diagnostic in diagnostics) { cancellationToken.ThrowIfCancellationRequested(); var node = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); - var stringValue = GetUtf8StringValueForDiagnostic(semanticModel, diagnostic, cancellationToken); + var arrayOp = GetArrayCreationOperation(semanticModel, diagnostic, cancellationToken); + Contract.ThrowIfNull(arrayOp.Initializer); + + var stringValue = GetUtf8StringValueFromArrayInitializer(arrayOp.Initializer); + + // If our array is parented by a conversion to ReadOnlySpan then we don't want to call + // ToArray after the string literal, or we'll be regressing perf. + var isConvertedToReadOnlySpan = arrayOp.Parent is IConversionOperation conversion && + conversion.Type is INamedTypeSymbol { IsGenericType: true } namedType && + namedType.OriginalDefinition.Equals(readOnlySpanType) && + namedType.TypeArguments[0].SpecialType == SpecialType.System_Byte; // If we're replacing a byte array that is passed to a parameter array, not and an explicit array creation // then node will be the ArgumentListSyntax that the implicit array creation is just a part of, so we have @@ -68,16 +82,16 @@ protected override async Task FixAllAsync( if (node is BaseArgumentListSyntax argumentList) { - editor.ReplaceNode(node, CreateArgumentListWithUtf8String(argumentList, diagnostic.Location, stringValue)); + editor.ReplaceNode(node, CreateArgumentListWithUtf8String(argumentList, diagnostic.Location, stringValue, isConvertedToReadOnlySpan)); } else { - editor.ReplaceNode(node, CreateUtf8String(node, stringValue)); + editor.ReplaceNode(node, CreateUtf8String(node, stringValue, isConvertedToReadOnlySpan)); } } } - private static string GetUtf8StringValueForDiagnostic(SemanticModel semanticModel, Diagnostic diagnostic, CancellationToken cancellationToken) + private static IArrayCreationOperation GetArrayCreationOperation(SemanticModel semanticModel, Diagnostic diagnostic, CancellationToken cancellationToken) { // For computing the UTF-8 string we need the original location of the array creation // operation, which is stored in additional locations. @@ -90,15 +104,13 @@ private static string GetUtf8StringValueForDiagnostic(SemanticModel semanticMode if (!Enum.TryParse(operationLocationString, out UseUtf8StringLiteralDiagnosticAnalyzer.ArrayCreationOperationLocation operationLocation)) throw ExceptionUtilities.Unreachable; - IArrayCreationOperation arrayOp; - // Because we get the location from an IOperation.Syntax, sometimes we have to look a // little harder to get back from syntax to the operation that triggered the diagnostic if (operationLocation == UseUtf8StringLiteralDiagnosticAnalyzer.ArrayCreationOperationLocation.Ancestors) { // For collection initializers where the Add method takes a param array, and the array creation // will be a parent of the operation - arrayOp = FindArrayCreationOperationAncestor(operation); + return FindArrayCreationOperationAncestor(operation); } else if (operationLocation == UseUtf8StringLiteralDiagnosticAnalyzer.ArrayCreationOperationLocation.Descendants) { @@ -106,29 +118,13 @@ private static string GetUtf8StringValueForDiagnostic(SemanticModel semanticMode // will be the invocation, or similar, that has the argument, and we need to descend child // nodes to find the one we are interested in. To make sure we're finding the right one, // we can use the diagnostic location for that, since the analyzer raises it on the first element. - arrayOp = operation.DescendantsAndSelf() + return operation.DescendantsAndSelf() .OfType() .Where(a => a.Initializer?.ElementValues.FirstOrDefault()?.Syntax.SpanStart == diagnostic.Location.SourceSpan.Start) .First(); } - else - { - arrayOp = (IArrayCreationOperation)operation; - } - Contract.ThrowIfNull(arrayOp.Initializer); - - // Get our list of bytes from the array elements - using var _ = PooledStringBuilder.GetInstance(out var builder); - builder.Capacity = arrayOp.Initializer.ElementValues.Length; - if (!UseUtf8StringLiteralDiagnosticAnalyzer.TryConvertToUtf8String(builder, arrayOp.Initializer.ElementValues)) - { - // We shouldn't get here, because the code fix shouldn't ask for a string value - // if the analyzer couldn't convert it - throw ExceptionUtilities.Unreachable; - } - - return builder.ToString(); + return (IArrayCreationOperation)operation; static IArrayCreationOperation FindArrayCreationOperationAncestor(IOperation operation) { @@ -144,7 +140,19 @@ static IArrayCreationOperation FindArrayCreationOperationAncestor(IOperation ope } } - private static SyntaxNode CreateArgumentListWithUtf8String(BaseArgumentListSyntax argumentList, Location location, string stringValue) + private static string GetUtf8StringValueFromArrayInitializer(IArrayInitializerOperation initializer) + { + // Get our list of bytes from the array elements + using var _ = PooledStringBuilder.GetInstance(out var builder); + builder.Capacity = initializer.ElementValues.Length; + + // Can never fail as the analyzer already validated this would work. + Contract.ThrowIfFalse(UseUtf8StringLiteralDiagnosticAnalyzer.TryConvertToUtf8String(builder, initializer.ElementValues)); + + return builder.ToString(); + } + + private static SyntaxNode CreateArgumentListWithUtf8String(BaseArgumentListSyntax argumentList, Location location, string stringValue, bool isConvertedToReadOnlySpan) { // To construct our new argument list we add any existing tokens before the location // and then once we hit the location, we add our string literal @@ -164,7 +172,7 @@ private static SyntaxNode CreateArgumentListWithUtf8String(BaseArgumentListSynta { // We don't need to worry about leading trivia here, because anything before the current // argument will have been trailing trivia on the previous comma. - var stringLiteral = CreateUtf8String(SyntaxTriviaList.Empty, stringValue, argumentList.Arguments.Last().GetTrailingTrivia()); + var stringLiteral = CreateUtf8String(SyntaxTriviaList.Empty, stringValue, argumentList.Arguments.Last().GetTrailingTrivia(), isConvertedToReadOnlySpan); arguments.Add(SyntaxFactory.Argument(stringLiteral)); break; } @@ -175,24 +183,32 @@ private static SyntaxNode CreateArgumentListWithUtf8String(BaseArgumentListSynta return argumentList.WithArguments(SyntaxFactory.SeparatedList(arguments)); } - private static InvocationExpressionSyntax CreateUtf8String(SyntaxNode nodeToTakeTriviaFrom, string stringValue) + private static ExpressionSyntax CreateUtf8String(SyntaxNode nodeToTakeTriviaFrom, string stringValue, bool isConvertedToReadOnlySpan) { - return CreateUtf8String(nodeToTakeTriviaFrom.GetLeadingTrivia(), stringValue, nodeToTakeTriviaFrom.GetTrailingTrivia()); + return CreateUtf8String(nodeToTakeTriviaFrom.GetLeadingTrivia(), stringValue, nodeToTakeTriviaFrom.GetTrailingTrivia(), isConvertedToReadOnlySpan); } - private static InvocationExpressionSyntax CreateUtf8String(SyntaxTriviaList leadingTrivia, string stringValue, SyntaxTriviaList trailingTrivia) + private static ExpressionSyntax CreateUtf8String(SyntaxTriviaList leadingTrivia, string stringValue, SyntaxTriviaList trailingTrivia, bool isConvertedToReadOnlySpan) { - var literal = SyntaxFactory.Token( + var stringLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.Utf8StringLiteralExpression, + SyntaxFactory.Token( leading: leadingTrivia, kind: SyntaxKind.Utf8StringLiteralToken, text: QuoteCharacter + stringValue + QuoteCharacter + Suffix, valueText: "", - trailing: SyntaxTriviaList.Empty); + trailing: SyntaxTriviaList.Empty)); + + if (isConvertedToReadOnlySpan) + { + return stringLiteral.WithTrailingTrivia(trailingTrivia); + } + // We're replacing a byte array with a ReadOnlySpan, so if that byte array wasn't originally being + // converted to the same, then we need to call .ToArray() to get things back to a byte array. return SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.LiteralExpression(SyntaxKind.Utf8StringLiteralExpression, literal), + stringLiteral, SyntaxFactory.IdentifierName(nameof(ReadOnlySpan.ToArray)))) .WithTrailingTrivia(trailingTrivia); } diff --git a/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs b/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs index 143b42bf932bb..75a4ca3e5a51c 100644 --- a/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs +++ b/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs @@ -611,5 +611,71 @@ internal class C : I await test.RunAsync(); } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record struct")] + [InlineData("interface")] + [InlineData("enum")] + [WorkItem(62259, "https://github.com/dotnet/roslyn/issues/62259")] + public async Task TestFileDeclaration(string declarationKind) + { + var source = $"file {declarationKind} C {{ }}"; + + await new VerifyCS.Test + { + TestCode = source, + FixedCode = source, + LanguageVersion = LanguageVersion.Preview, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)] + [WorkItem(62259, "https://github.com/dotnet/roslyn/issues/62259")] + public async Task TestFileDelegate() + { + var source = "file delegate void M();"; + + await new VerifyCS.Test + { + TestCode = source, + FixedCode = source, + LanguageVersion = LanguageVersion.Preview, + }.RunAsync(); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record struct")] + [InlineData("interface")] + [InlineData("enum")] + [WorkItem(62259, "https://github.com/dotnet/roslyn/issues/62259")] + public async Task TestNestedFileDeclaration(string declarationKind) + { + var source = $$""" + file class C1 + { + {{declarationKind}} [|C2|] { } + } + """; + + var fixedSource = $$""" + file class C1 + { + private {{declarationKind}} C2 { } + } + """; + + await new VerifyCS.Test + { + TestCode = source, + FixedCode = fixedSource, + LanguageVersion = LanguageVersion.Preview, + }.RunAsync(); + } } } diff --git a/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs b/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs index 7f423f75ec32b..d63497c7420ce 100644 --- a/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs +++ b/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs @@ -174,6 +174,27 @@ class Class1 directory: folder); } + [Fact] + public async Task CodeStyleOptionIsFalse() + { + var folder = CreateFolderPath("B", "C"); + var code = +@"namespace A.B +{ + class Class1 + { + } +}"; + + await RunTestAsync( + fileName: "Class1.cs", + fileContents: code, + directory: folder, + editorConfig: EditorConfig + @" +dotnet_style_namespace_match_folder = false" +); + } + [Fact] public async Task SingleDocumentNoReference() { diff --git a/src/Analyzers/CSharp/Tests/RemoveUnreachableCode/RemoveUnreachableCodeTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnreachableCode/RemoveUnreachableCodeTests.cs index cf12459612d19..6b5cc762771c1 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnreachableCode/RemoveUnreachableCodeTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnreachableCode/RemoveUnreachableCodeTests.cs @@ -2,41 +2,32 @@ // 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.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.RemoveUnreachableCode; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; -using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnreachableCode { - public class RemoveUnreachableCodeTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest - { - public RemoveUnreachableCodeTests(ITestOutputHelper logger) - : base(logger) - { - } - - internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new CSharpRemoveUnreachableCodeDiagnosticAnalyzer(), new CSharpRemoveUnreachableCodeCodeFixProvider()); + using VerifyCS = CSharpCodeFixVerifier; + public class RemoveUnreachableCodeTests + { [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestSingleUnreachableStatement() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - } +[| var v = 0; +|] } }", @" class C @@ -51,7 +42,7 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestInUnreachableIfBody() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -59,8 +50,8 @@ void M() { if (false) { - [|var v = 0;|] - } +[| var v = 0; +|] } } }", @" @@ -78,15 +69,15 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestInIfWithNoBlock() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { if (false) - [|var v = 0;|] - } +[| {|CS1023:var v = 0;|} +|] } }", @" class C @@ -103,16 +94,16 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestRemoveSubsequentStatements() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - var y = 1; - } +[| var v = 0; +|][| var y = 1; +|] } }", @" class C @@ -127,16 +118,16 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestFromSubsequentStatement() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - var v = 0; - [|var y = 1;|] - } +[| var v = 0; +|][| var y = 1; +|] } }", @" class C @@ -151,19 +142,19 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestRemoveSubsequentStatementsExcludingLocalFunction() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - +[| var v = 0; +|] void Local() {} - +[| var y = 1; - } +|] } }", @" class C @@ -180,20 +171,20 @@ void Local() {} [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestRemoveSubsequentStatementsExcludingMultipleLocalFunctions() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - +[| var v = 0; +|] void Local() {} void Local2() {} - +[| var y = 1; - } +|] } }", @" class C @@ -211,23 +202,23 @@ void Local2() {} [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestRemoveSubsequentStatementsInterspersedWithMultipleLocalFunctions() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - +[| var v = 0; +|] void Local() {} - +[| var z = 2; - +|] void Local2() {} - +[| var y = 1; - } +|] } }", @" class C @@ -246,24 +237,24 @@ void Local2() {} [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestRemoveSubsequentStatementsInterspersedWithMultipleLocalFunctions2() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - +[| var v = 0; +|] void Local() {} - +[| var z = 2; var z2 = 2; - +|] void Local2() {} - +[| var y = 1; - } +|] } }", @" class C @@ -282,20 +273,20 @@ void Local2() {} [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestRemoveSubsequentStatementsUpToNextLabel() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - +[| var v = 0; +|][| label: - Console.WriteLine(); - + System.Console.WriteLine(); +|][| var y = 1; - } +|] } }", @" class C @@ -303,11 +294,6 @@ class C void M() { throw new System.Exception(); - - label: - Console.WriteLine(); - - var y = 1; } }"); } @@ -315,20 +301,20 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestOnUnreachableLabel() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - var v = 0; - - [|label|]: - Console.WriteLine(); - +[| var v = 0; +|][| + label: + System.Console.WriteLine(); +|][| var y = 1; - } +|] } }", @" class C @@ -336,7 +322,6 @@ class C void M() { throw new System.Exception(); - var v = 0; } }"); } @@ -344,8 +329,7 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestMissingOnReachableLabel() { - await TestMissingInRegularAndScriptAsync( -@" + var code = @" class C { void M(object o) @@ -356,20 +340,39 @@ void M(object o) } throw new System.Exception(); - var v = 0; +[| var v = 0; +|] + label: + System.Console.WriteLine(); - [|label|]: - Console.WriteLine(); + var y = 1; + } +}"; + var fixedCode = @" +class C +{ + void M(object o) + { + if (o != null) + { + goto label; + } + + throw new System.Exception(); + + label: + System.Console.WriteLine(); var y = 1; } -}"); +}"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestInLambda() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" using System; @@ -380,9 +383,9 @@ void M() Action a = () => { if (true) return; - - [|Console.WriteLine();|] - }; +[| + Console.WriteLine(); +|] }; } }", @" @@ -403,7 +406,7 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestInLambdaInExpressionBody() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" using System; @@ -413,9 +416,9 @@ Action M() => () => { if (true) return; - - [|Console.WriteLine();|] - }; +[| + Console.WriteLine(); +|] }; }", @" using System; @@ -433,20 +436,20 @@ Action M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestSingleRemovalDoesNotTouchCodeInUnrelatedLocalFunction() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - [|var v = 0;|] - +[| var v = 0; +|] void Local() { throw new System.Exception(); - var x = 0; - } +[| var x = 0; +|] } } }", @" @@ -459,7 +462,6 @@ void M() void Local() { throw new System.Exception(); - var x = 0; } } }"); @@ -468,20 +470,20 @@ void Local() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestFixAll1() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - {|FixAllInDocument:var v = 0;|} - +[| var v = 0; +|] void Local() { throw new System.Exception(); - var x = 0; - } +[| var x = 0; +|] } } }", @" @@ -502,7 +504,7 @@ void Local() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestFixAll2() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -514,13 +516,13 @@ void M(object o) } throw new System.Exception(); - {|FixAllInDocument:var v = 0;|} - +[| var v = 0; +|][| UnreachableLabel: - Console.WriteLine(x); - + System.Console.WriteLine(o); +|] ReachableLabel: - Console.WriteLine(y); + System.Console.WriteLine(o.ToString()); } }", @" @@ -536,7 +538,7 @@ void M(object o) throw new System.Exception(); ReachableLabel: - Console.WriteLine(y); + System.Console.WriteLine(o.ToString()); } }"); } @@ -544,7 +546,7 @@ void M(object o) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestFixAll3() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -556,19 +558,19 @@ void M(object o) } throw new System.Exception(); - {|FixAllInDocument:var v = 0;|} - +[| var v = 0; +|] ReachableLabel1: - Console.WriteLine(x); + System.Console.WriteLine(o); ReachableLabel2: { - Console.WriteLine(y); + System.Console.WriteLine(o.ToString()); goto ReachableLabel1; } - +[| var x = 1; - } +|] } }", @" class C @@ -583,11 +585,11 @@ void M(object o) throw new System.Exception(); ReachableLabel1: - Console.WriteLine(x); + System.Console.WriteLine(o); ReachableLabel2: { - Console.WriteLine(y); + System.Console.WriteLine(o.ToString()); goto ReachableLabel1; } } @@ -597,7 +599,7 @@ void M(object o) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestFixAll4() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -608,15 +610,14 @@ void M(object o) for (int j = 0; j < 10; j = j + 1) { goto stop; - {|FixAllInDocument:goto outerLoop;|} - } +[| goto outerLoop; +|] } outerLoop: return; } stop: return; } - } }", @" class C @@ -635,25 +636,24 @@ void M(object o) stop: return; } - } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestFixAll5() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M(object o) { if (false) - throw new Exception(); + throw new System.Exception(); - throw new Exception(); - {|FixAllInDocument:return;|} - } + throw new System.Exception(); +[| return; +|] } }", @" class C @@ -661,9 +661,9 @@ class C void M(object o) { if (false) - throw new Exception(); + throw new System.Exception(); - throw new Exception(); + throw new System.Exception(); } }"); } @@ -671,7 +671,7 @@ void M(object o) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestInUnreachableInSwitchSection1() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -680,10 +680,10 @@ void M(int i) switch (i) { case 0: - throw new Exception(); - [|var v = 0;|] - break; - } + throw new System.Exception(); +[| var v = 0; +|][| break; +|] } } }", @" @@ -694,7 +694,7 @@ void M(int i) switch (i) { case 0: - throw new Exception(); + throw new System.Exception(); } } }"); @@ -703,17 +703,17 @@ void M(int i) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestDirectives1() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { throw new System.Exception(); - +[| #if true - [|var v = 0;|] -#endif + var v = 0; +|]#endif } }", @" @@ -732,7 +732,7 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestDirectives2() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -740,8 +740,8 @@ void M() { #if true throw new System.Exception(); - [|var v = 0;|] -#endif +[| var v = 0; +|]#endif } }", @" @@ -759,7 +759,7 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestDirectives3() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -767,9 +767,9 @@ void M() { #if true throw new System.Exception(); -#endif - [|var v = 0;|] - } +[|#endif + var v = 0; +|] } }", @" class C @@ -787,7 +787,7 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestForLoop1() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { @@ -797,9 +797,9 @@ void M() { i = 2; goto Lab2; - [|i = 1;|] - break; - Lab2: +[| i = 1; +|][| break; +|] Lab2: return ; } } @@ -823,15 +823,15 @@ void M() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] public async Task TestInfiniteForLoop() { - await TestInRegularAndScript1Async( + await VerifyCS.VerifyCodeFixAsync( @" class C { void M() { for (;;) { } - [|return;|] - } +[| return; +|] } }", @" class C @@ -842,5 +842,157 @@ void M() } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] + [WorkItem(61810, "https://github.com/dotnet/roslyn/issues/61810")] + public async Task TestTopLevel_EndingWithNewLine() + { + var code = @" +throw new System.Exception(); +[|System.Console.ReadLine(); +|]"; + var fixedCode = @" +throw new System.Exception(); +"; + await new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + }, + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] + [WorkItem(61810, "https://github.com/dotnet/roslyn/issues/61810")] + public async Task TestTopLevel_NotEndingWithNewLine() + { + var code = @" +throw new System.Exception(); +[|System.Console.ReadLine();|]"; + var fixedCode = @" +throw new System.Exception(); +"; + await new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + }, + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] + [WorkItem(61810, "https://github.com/dotnet/roslyn/issues/61810")] + public async Task TestTopLevel_MultipleUnreachableStatements() + { + var code = @" +throw new System.Exception(); +[|System.Console.ReadLine(); +|][|System.Console.ReadLine(); +|]"; + var fixedCode = @" +throw new System.Exception(); +"; + await new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + }, + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] + [WorkItem(61810, "https://github.com/dotnet/roslyn/issues/61810")] + public async Task TestTopLevel_MultipleUnreachableStatements_HasClassDeclarationInBetween() + { + var code = @" +throw new System.Exception(); +[|System.Console.ReadLine(); +|] + +public class C { } +[| +{|CS8803:System.Console.ReadLine();|}|]"; + var fixedCode = @" +throw new System.Exception(); + + +public class C { } +"; + await new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + }, + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] + [WorkItem(61810, "https://github.com/dotnet/roslyn/issues/61810")] + public async Task TestTopLevel_MultipleUnreachableStatements_AfterClassDeclaration1() + { + var code = @" +throw new System.Exception(); + +public class C { } +[| +{|CS8803:System.Console.ReadLine();|}|]"; + var fixedCode = @" +throw new System.Exception(); + +public class C { } +"; + await new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + }, + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnreachableCode)] + [WorkItem(61810, "https://github.com/dotnet/roslyn/issues/61810")] + public async Task TestTopLevel_MultipleUnreachableStatements_AfterClassDeclaration2() + { + var code = @" +public class C { } + +{|CS8803:throw new System.Exception();|} +[|System.Console.ReadLine();|]"; + var fixedCode = @" +public class C { } + +{|CS8803:throw new System.Exception();|} +"; + await new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + }, + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } } } diff --git a/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundCoalesceAssignmentTests.cs b/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundCoalesceAssignmentTests.cs index 4169e9b17d88a..9b480405e8d68 100644 --- a/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundCoalesceAssignmentTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundCoalesceAssignmentTests.cs @@ -634,6 +634,64 @@ static void Main(object o) // Before o ??= new C(); // After } +}"); + } + + [WorkItem(32985, "https://github.com/dotnet/roslyn/issues/32985")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCompoundAssignment)] + public async Task TestIfStatement_Trivia2() + { + await TestInRegularAndScriptAsync( +@"using System; +class C +{ + static void Main(object o) + { + [|if|] (o is null) + { + // Before + o = new C(); // After + } + } +}", +@"using System; +class C +{ + static void Main(object o) + { + // Before + o ??= new C(); // After + } +}"); + } + + [WorkItem(32985, "https://github.com/dotnet/roslyn/issues/32985")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCompoundAssignment)] + public async Task TestIfStatement_Trivia3() + { + await TestInRegularAndScriptAsync( +@"using System; +class C +{ + static void Main(object o) + { + // Before1 + [|if|] (o is null) + { + // Before2 + o = new C(); // After + } + } +}", +@"using System; +class C +{ + static void Main(object o) + { + // Before1 + // Before2 + o ??= new C(); // After + } }"); } } diff --git a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs index b4223e2819911..57a79dc89ca31 100644 --- a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs +++ b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs @@ -2,30 +2,55 @@ // 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.CodeFixes; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.AddAccessibilityModifiers; using Microsoft.CodeAnalysis.CSharp.UseNullPropagation; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseNullPropagation { - public partial class UseNullPropagationTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + using VerifyCS = CSharpCodeFixVerifier< + CSharpUseNullPropagationDiagnosticAnalyzer, + CSharpUseNullPropagationCodeFixProvider>; + + public partial class UseNullPropagationTests { - public UseNullPropagationTests(ITestOutputHelper logger) - : base(logger) + private static async Task TestInRegularAndScript1Async(string testCode, string fixedCode, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = fixedCode, + // code action is currently generating invalid trees. Specifically, it transforms `x.Y()` into `x.?Y()` + // by just rewriting `x.Y` into `x?.Y`. That is not correct. the RHS of the `?` should `.Y()` not + // `.Y`. + CodeActionValidationMode = CodeActionValidationMode.None, + LanguageVersion = LanguageVersion.CSharp9, + TestState = + { + OutputKind = outputKind, + }, + }.RunAsync(); } - internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new CSharpUseNullPropagationDiagnosticAnalyzer(), new CSharpUseNullPropagationCodeFixProvider()); + private static async Task TestMissingInRegularAndScriptAsync(string testCode, LanguageVersion languageVersion = LanguageVersion.CSharp9) + { + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = testCode, + LanguageVersion = languageVersion, + }.RunAsync(); + } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] public async Task TestLeft_Equals() @@ -37,7 +62,7 @@ class C { void M(object o) { - var v = [||]o == null ? null : o.ToString(); + var v = [|o == null ? null : o.ToString()|]; } }", @"using System; @@ -52,22 +77,7 @@ void M(object o) } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestMissingOnCSharp5() - { - await TestMissingAsync( -@"using System; - -class C -{ - void M(object o) - { - var v = [||]o == null ? null : o.ToString(); - } -}", new TestParameters(parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5))); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestRight_Equals() + public async Task TestLeft_Equals_IfStatement() { await TestInRegularAndScript1Async( @"using System; @@ -76,7 +86,8 @@ class C { void M(object o) { - var v = [||]null == o ? null : o.ToString(); + [|if|] (o != null) + o.ToString(); } }", @"using System; @@ -85,13 +96,13 @@ class C { void M(object o) { - var v = o?.ToString(); + o?.ToString(); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestLeft_NotEquals() + public async Task TestIfStatement_WithBlock() { await TestInRegularAndScript1Async( @"using System; @@ -100,7 +111,10 @@ class C { void M(object o) { - var v = [||]o != null ? o.ToString() : null; + [|if|] (o != null) + { + o.ToString(); + } } }", @"using System; @@ -109,61 +123,68 @@ class C { void M(object o) { - var v = o?.ToString(); + o?.ToString(); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableType() + public async Task TestIfStatement_NotWithElse() { - await TestInRegularAndScript1Async( -@" -class C -{ - public int? f; - void M(C c) - { - int? x = [||]c != null ? c.f : null; - } -}", -@" + await TestMissingInRegularAndScriptAsync( +@"using System; + class C { - public int? f; - void M(C c) + void M(object o) { - int? x = c?.f; + if (o != null) + o.ToString(); + else + { + } } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndObjectCast() + public async Task TestIfStatement_NotWithMultipleStatements() { - await TestInRegularAndScript1Async( -@" -class C -{ - public int? f; - void M(C c) - { - int? x = (object)[||]c != null ? c.f : null; - } -}", -@" + await TestMissingInRegularAndScriptAsync( +@"using System; + class C { - public int? f; - void M(C c) + void M(object o) { - int? x = c?.f; + if (o != null) + { + o.ToString(); + o.ToString(); + } } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestRight_NotEquals() + public async Task TestLeft_Equals_IfStatement_TopLevel() + { + await TestInRegularAndScript1Async( +@"using System; + +object o = null; +[|if|] (o != null) + o.ToString(); +", +@"using System; + +object o = null; +o?.ToString(); +", OutputKind.ConsoleApplication); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestLeft_IsNull() { await TestInRegularAndScript1Async( @"using System; @@ -172,7 +193,7 @@ class C { void M(object o) { - var v = [||]null != o ? o.ToString() : null; + var v = [|o is null ? null : o.ToString()|]; } }", @"using System; @@ -187,7 +208,7 @@ void M(object o) } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestIndexer() + public async Task TestLeft_IsNotNull() { await TestInRegularAndScript1Async( @"using System; @@ -196,7 +217,7 @@ class C { void M(object o) { - var v = [||]o == null ? null : o[0]; + var v = [|o is not null ? o.ToString() : null|]; } }", @"using System; @@ -205,13 +226,13 @@ class C { void M(object o) { - var v = o?[0]; + var v = o?.ToString(); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestConditionalAccess() + public async Task TestLeft_IsNotNull_IfStatement() { await TestInRegularAndScript1Async( @"using System; @@ -220,7 +241,8 @@ class C { void M(object o) { - var v = [||]o == null ? null : o.B?.C; + [|if|] (o is not null) + o.ToString(); } }", @"using System; @@ -229,37 +251,28 @@ class C { void M(object o) { - var v = o?.B?.C; + o?.ToString(); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestMemberAccess() + public async Task TestMissingOnCSharp5() { - await TestInRegularAndScript1Async( -@"using System; - -class C -{ - void M(object o) - { - var v = [||]o == null ? null : o.B; - } -}", + await TestMissingInRegularAndScriptAsync( @"using System; class C { void M(object o) { - var v = o?.B; + var v = o == null ? null : o.ToString(); } -}"); +}", LanguageVersion.CSharp5); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestMissingOnSimpleMatch() + public async Task TestMissingOnCSharp5_IfStatement() { await TestMissingInRegularAndScriptAsync( @"using System; @@ -268,13 +281,14 @@ class C { void M(object o) { - var v = [||]o == null ? null : o; + if (o != null) + o.ToString(); } -}"); +}", LanguageVersion.CSharp5); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestParenthesizedCondition() + public async Task TestRight_Equals() { await TestInRegularAndScript1Async( @"using System; @@ -283,7 +297,7 @@ class C { void M(object o) { - var v = [||](o == null) ? null : o.ToString(); + var v = [|null == o ? null : o.ToString()|]; } }", @"using System; @@ -298,7 +312,7 @@ void M(object o) } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestFixAll1() + public async Task TestRight_Equals_IfStatement() { await TestInRegularAndScript1Async( @"using System; @@ -307,8 +321,8 @@ class C { void M(object o) { - var v1 = {|FixAllInDocument:o|} == null ? null : o.ToString(); - var v2 = o != null ? o.ToString() : null; + [|if|] (null != o) + o.ToString(); } }", @"using System; @@ -317,181 +331,620 @@ class C { void M(object o) { - var v1 = o?.ToString(); - var v2 = o?.ToString(); + o?.ToString(); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestFixAll2() + public async Task TestLeft_NotEquals() { await TestInRegularAndScript1Async( @"using System; class C { - void M(object o1, object o2) + void M(object o) { - var v1 = {|FixAllInDocument:o1|} == null ? null : o1.ToString(o2 == null ? null : o2.ToString()); + var v = [|o != null ? o.ToString() : null|]; } }", @"using System; class C { - void M(object o1, object o2) + void M(object o) { - var v1 = o1?.ToString(o2?.ToString()); + var v = o?.ToString(); } }"); } - [WorkItem(15505, "https://github.com/dotnet/roslyn/issues/15505")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestOtherValueIsNotNull1() + public async Task TestWithNullableType() { - await TestMissingInRegularAndScriptAsync( -@"using System; - + await TestInRegularAndScript1Async( +@" class C { - void M(object o) + public int? f; + void M(C c) + { + int? x = [|c != null ? c.f : null|]; + } +}", +@" +class C +{ + public int? f; + void M(C c) { - var v = [||]o == null ? 0 : o.ToString(); + int? x = c?.f; } }"); } - [WorkItem(15505, "https://github.com/dotnet/roslyn/issues/15505")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestOtherValueIsNotNull2() + public async Task TestWithNullableType_IfStatement() { - await TestMissingInRegularAndScriptAsync( -@"using System; - + await TestInRegularAndScript1Async( +@" class C { - void M(object o) + public int? f; + void M(C c) + { + [|if|] (c != null) + c.f?.ToString(); + } +}", +@" +class C +{ + public int? f; + void M(C c) { - var v = [||]o != null ? o.ToString() : 0; + c?.f?.ToString(); } }"); } - [WorkItem(16287, "https://github.com/dotnet/roslyn/issues/16287")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestMethodGroup() + public async Task TestWithNullableTypeAndObjectCast() { - await TestMissingInRegularAndScriptAsync( + await TestInRegularAndScript1Async( @" -using System; - -class D +class C { - void Goo() + public int? f; + void M(C c) { - var c = new C(); - Action a = [||]c != null ? c.M : (Action)null; + int? x = [|(object)c != null ? c.f : null|]; } -} -class C { public void M(string s) { } }"); +}", +@" +class C +{ + public int? f; + void M(C c) + { + int? x = c?.f; + } +}"); } - [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestInExpressionTree() + public async Task TestWithNullableTypeAndObjectCast_IfStatement() { - await TestMissingInRegularAndScriptAsync( + await TestInRegularAndScript1Async( @" -using System; -using System.Linq.Expressions; - -class Program +class C { - void Main(string s) + public int? f; + void M(C c) { - Method(t => [||]s != null ? s.ToString() : null); // works + [|if|] ((object)c != null) + c.f?.ToString(); } - - public void Method(Expression> functor) +}", +@" +class C +{ + public int? f; + void M(C c) { + c?.f?.ToString(); } }"); } - [WorkItem(33992, "https://github.com/dotnet/roslyn/issues/33992")] - [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestInExpressionTree2() + public async Task TestRight_NotEquals() { - await TestMissingInRegularAndScriptAsync( -@" -using System.Linq; + await TestInRegularAndScript1Async( +@"using System; class C { - void Main() + void M(object o) { - _ = from item in Enumerable.Empty<(int? x, int? y)?>().AsQueryable() - select [||]item == null ? null : item.Value.x; + var v = [|null != o ? o.ToString() : null|]; + } +}", +@"using System; + +class C +{ + void M(object o) + { + var v = o?.ToString(); } }"); } - [WorkItem(33992, "https://github.com/dotnet/roslyn/issues/33992")] - [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestInExpressionTree3() + public async Task TestIndexer() { - await TestMissingInRegularAndScriptAsync( -@" -using System.Linq; + await TestInRegularAndScript1Async( +@"using System; class C { - void Main() + void M(object o) { - _ = from item in Enumerable.Empty<(int? x, int? y)?>().AsQueryable() - where ([||]item == null ? null : item.Value.x) > 0 - select item; + var v = [|o == null ? null : {|CS0021:o[0]|}|]; + } +}", +@"using System; + +class C +{ + void M(object o) + { + var v = o?{|CS0021:[0]|}; } }"); } - [WorkItem(33992, "https://github.com/dotnet/roslyn/issues/33992")] - [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestInExpressionTree4() + public async Task TestIndexer_IfStatement() { - await TestMissingInRegularAndScriptAsync( -@" -using System.Linq; + await TestInRegularAndScript1Async( +@"using System; class C { - void Main() + void M(object o) { - _ = from item in Enumerable.Empty<(int? x, int? y)?>().AsQueryable() - let x = [||]item == null ? null : item.Value.x - select x; + [|if|] (o != null) + {|CS0021:o[0]|}.ToString(); + } +}", +@"using System; + +class C +{ + void M(object o) + { + o?{|CS0021:[0]|}.ToString(); } }"); } - [WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestNullableMemberAccess() + public async Task TestConditionalAccess() { await TestInRegularAndScript1Async( -@" -using System; +@"using System; class C { - void Main(DateTime? toDate) + void M(object o) { - var v = [||]toDate == null ? null : toDate.Value.ToString(""yyyy/MM/ dd""); + var v = [|o == null ? null : o.{|CS1061:B|}?.C|]; + } +}", +@"using System; + +class C +{ + void M(object o) + { + var v = o?{|CS1061:.B|}?.C; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestConditionalAccess_IfStatement() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + [|if|](o != null) + o.{|CS1061:B|}?.C(); + } +}", +@"using System; + +class C +{ + void M(object o) + { + o?{|CS1061:.B|}?.C(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestMemberAccess() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + var v = [|o == null ? null : o.{|CS1061:B|}|]; + } +}", +@"using System; + +class C +{ + void M(object o) + { + var v = o?{|CS1061:.B|}; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestMemberAccess_IfStatement() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + [|if|] (o != null) + o.{|CS1061:B|}(); + } +}", +@"using System; + +class C +{ + void M(object o) + { + o?{|CS1061:.B|}(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestMissingOnSimpleMatch() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void M(object o) + { + var v = o == null ? null : o; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestMissingOnSimpleMatch_IfStatement() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void M(object o) + { + if (o != null) + {|CS0201:o|}; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestParenthesizedCondition() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + var v = [|(o == null) ? null : o.ToString()|]; + } +}", +@"using System; + +class C +{ + void M(object o) + { + var v = o?.ToString(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestFixAll1() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + var v1 = [|o == null ? null : o.ToString()|]; + var v2 = [|o != null ? o.ToString() : null|]; + } +}", +@"using System; + +class C +{ + void M(object o) + { + var v1 = o?.ToString(); + var v2 = o?.ToString(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestFixAll2() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o1, object o2) + { + var v1 = [|o1 == null ? null : o1.{|CS1501:ToString|}([|o2 == null ? null : o2.ToString()|])|]; + } +}", +@"using System; + +class C +{ + void M(object o1, object o2) + { + var v1 = o1?{|CS1501:.ToString|}(o2?.ToString()); + } +}"); + } + + [WorkItem(15505, "https://github.com/dotnet/roslyn/issues/15505")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestOtherValueIsNotNull1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void M(object o) + { + var v = {|CS0173:o == null ? 0 : o.ToString()|}; + } +}"); + } + + [WorkItem(15505, "https://github.com/dotnet/roslyn/issues/15505")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestOtherValueIsNotNull2() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void M(object o) + { + var v = {|CS0173:o != null ? o.ToString() : 0|}; + } +}"); + } + + [WorkItem(16287, "https://github.com/dotnet/roslyn/issues/16287")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestMethodGroup() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class D +{ + void Goo() + { + var c = new C(); + Action a = c != null ? c.M : (Action)null; + } +} +class C { public void M(string s) { } }"); + } + + [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestInExpressionTree() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; +using System.Linq.Expressions; + +class Program +{ + void Main(string s) + { + Method(t => s != null ? s.ToString() : null); // works + } + + public void Method(Expression> functor) + { + } +}"); + } + + [WorkItem(33992, "https://github.com/dotnet/roslyn/issues/33992")] + [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestInExpressionTree2() + { + await TestMissingInRegularAndScriptAsync( +@" +using System.Linq; + +class C +{ + void Main() + { + _ = from item in Enumerable.Empty<(int? x, int? y)?>().AsQueryable() + select item == null ? null : item.Value.x; + } +}"); + } + + [WorkItem(33992, "https://github.com/dotnet/roslyn/issues/33992")] + [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestInExpressionTree3() + { + await TestMissingInRegularAndScriptAsync( +@" +using System.Linq; + +class C +{ + void Main() + { + _ = from item in Enumerable.Empty<(int? x, int? y)?>().AsQueryable() + where (item == null ? null : item.Value.x) > 0 + select item; + } +}"); + } + + [WorkItem(33992, "https://github.com/dotnet/roslyn/issues/33992")] + [WorkItem(17623, "https://github.com/dotnet/roslyn/issues/17623")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestInExpressionTree4() + { + await TestMissingInRegularAndScriptAsync( +@" +using System.Linq; + +class C +{ + void Main() + { + _ = from item in Enumerable.Empty<(int? x, int? y)?>().AsQueryable() + let x = item == null ? null : item.Value.x + select x; + } +}"); + } + + [WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestNullableMemberAccess() + { + await TestInRegularAndScript1Async( +@" +using System; + +class C +{ + void Main(DateTime? toDate) + { + var v = [|toDate == null ? null : toDate.Value.ToString(""yyyy/MM/ dd"")|]; + } +} +", + +@" +using System; + +class C +{ + void Main(DateTime? toDate) + { + var v = toDate?.ToString(""yyyy/MM/ dd""); + } +} +"); + } + + [WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestNullableMemberAccess_IfStatement() + { + await TestInRegularAndScript1Async( +@" +using System; + +class C +{ + void Main(DateTime? toDate) + { + [|if|] (toDate != null) + toDate.Value.ToString(""yyyy/MM/ dd""); + } +} +", + +@" +using System; + +class C +{ + void Main(DateTime? toDate) + { + toDate?.ToString(""yyyy/MM/ dd""); + } +} +"); + } + + [WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestNullableElementAccess() + { + await TestInRegularAndScript1Async( +@" +using System; + +struct S +{ + public string this[int i] => """"; +} + +class C +{ + void Main(S? s) + { + var x = [|s == null ? null : s.Value[0]|]; } } ", @@ -499,59 +952,258 @@ void Main(DateTime? toDate) @" using System; +struct S +{ + public string this[int i] => """"; +} + +class C +{ + void Main(S? s) + { + var x = s?[0]; + } +} +"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsNull() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + int? x = [|c is null ? null : c.f|]; + } +}", +@" +class C +{ + public int? f; + void M(C c) + { + int? x = c?.f; + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsNotNull() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + int? x = [|c is not null ? c.f : null|]; + } +}", +@" +class C +{ + public int? f; + void M(C c) + { + int? x = c?.f; + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsNotNull_IfStatement() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + [|if|] (c is not null) + c.f?.ToString(); + } +}", +@" +class C +{ + public int? f; + void M(C c) + { + c?.f?.ToString(); + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsType() + { + await TestMissingInRegularAndScriptAsync( +@" +class C +{ + public int? f; + void M(C c) + { + int? x = c is C ? null : c.f; + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsType_IfStatement1() + { + await TestMissingInRegularAndScriptAsync( +@" +class C +{ + public int? f; + void M(C c) + { + if (c is C) + c.f?.ToString(); + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsType_IfStatement2() + { + await TestMissingInRegularAndScriptAsync( +@" +class C +{ + public int? f; + void M(C c) + { + if (c is C d) + c.f?.ToString(); + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndIsType_IfStatement3() + { + await TestMissingInRegularAndScriptAsync( +@" +class C +{ + public int? f; + void M(C c) + { + if (c is not C) + c.f?.ToString(); + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestIsOtherConstant() + { + await TestMissingInRegularAndScriptAsync( +@" +class C +{ + void M(string s) + { + int? x = s is """" ? null : (int?)s.Length; + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndReferenceEquals1() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + int? x = [|ReferenceEquals(c, null) ? null : c.f|]; + } +}", +@" +class C +{ + public int? f; + void M(C c) + { + int? x = c?.f; + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndReferenceEquals1_IfStatement() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + [|if|] (!ReferenceEquals(c, null)) + c.f?.ToString(); + } +}", +@" class C { - void Main(DateTime? toDate) + public int? f; + void M(C c) { - var v = toDate?.ToString(""yyyy/MM/ dd""); + c?.f?.ToString(); } -} -"); +}"); } - [WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")] + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestNullableElementAccess() + public async Task TestWithNullableTypeAndReferenceEquals2() { await TestInRegularAndScript1Async( @" -using System; - -struct S -{ - public string this[int i] => """"; -} - class C { - void Main(S? s) + public int? f; + void M(C c) { - var x = [||]s == null ? null : s.Value[0]; + int? x = [|ReferenceEquals(null, c) ? null : c.f|]; } -} -", - +}", @" -using System; - -struct S -{ - public string this[int i] => """"; -} - class C { - void Main(S? s) + public int? f; + void M(C c) { - var x = s?[0]; + int? x = c?.f; } -} -"); +}"); } [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndIsNull() + public async Task TestWithNullableTypeAndReferenceEquals2_IfStatement() { await TestInRegularAndScript1Async( @" @@ -560,7 +1212,8 @@ class C public int? f; void M(C c) { - int? x = [||]c is null ? null : c.f; + [|if|] (!ReferenceEquals(null, c)) + c.f?.ToString(); } }", @" @@ -569,70 +1222,80 @@ class C public int? f; void M(C c) { - int? x = c?.f; + c?.f?.ToString(); } }"); } [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndIsType() + public async Task TestWithNullableTypeAndReferenceEqualsOtherValue1() { await TestMissingInRegularAndScriptAsync( @" class C { public int? f; - void M(C c) + void M(C c, C other) { - int? x = [||]c is C ? null : c.f; + int? x = ReferenceEquals(c, other) ? null : c.f; } }"); } [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestIsOtherConstant() + public async Task TestWithNullableTypeAndReferenceEqualsOtherValue1_IfStatement1() { await TestMissingInRegularAndScriptAsync( @" class C { - void M(string s) + public int? f; + void M(C c, C other) { - int? x = [||]s is """" ? null : (int?)s.Length; + if (ReferenceEquals(c, other)) + c.f?.ToString(); } }"); } [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndReferenceEquals1() + public async Task TestWithNullableTypeAndReferenceEqualsOtherValue1_IfStatement2() { - await TestInRegularAndScript1Async( + await TestMissingInRegularAndScriptAsync( @" class C { public int? f; - void M(C c) + void M(C c, C other) { - int? x = [||]ReferenceEquals(c, null) ? null : c.f; + if (!ReferenceEquals(c, other)) + c.f?.ToString(); } -}", +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndReferenceEqualsOtherValue2() + { + await TestMissingInRegularAndScriptAsync( @" class C { public int? f; - void M(C c) + void M(C c, C other) { - int? x = c?.f; + int? x = ReferenceEquals(other, c) ? null : c.f; } }"); } [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndReferenceEquals2() + public async Task TestWithNullableTypeAndReferenceEqualsWithObject1() { await TestInRegularAndScript1Async( @" @@ -641,7 +1304,7 @@ class C public int? f; void M(C c) { - int? x = [||]ReferenceEquals(null, c) ? null : c.f; + int? x = [|object.ReferenceEquals(c, null) ? null : c.f|]; } }", @" @@ -657,39 +1320,33 @@ void M(C c) [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndReferenceEqualsOtherValue1() + public async Task TestWithNullableTypeAndReferenceEqualsWithObject1_IfStatement() { - await TestMissingInRegularAndScriptAsync( + await TestInRegularAndScript1Async( @" class C { public int? f; - void M(C c, C other) + void M(C c) { - int? x = [||]ReferenceEquals(c, other) ? null : c.f; + [|if|] (!object.ReferenceEquals(c, null)) + c.f?.ToString(); } -}"); - } - - [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndReferenceEqualsOtherValue2() - { - await TestMissingInRegularAndScriptAsync( +}", @" class C { public int? f; - void M(C c, C other) + void M(C c) { - int? x = [||]ReferenceEquals(other, c) ? null : c.f; + c?.f?.ToString(); } }"); } [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndReferenceEqualsWithObject1() + public async Task TestWithNullableTypeAndReferenceEqualsWithObject2() { await TestInRegularAndScript1Async( @" @@ -698,7 +1355,7 @@ class C public int? f; void M(C c) { - int? x = [||]object.ReferenceEquals(c, null) ? null : c.f; + int? x = [|object.ReferenceEquals(null, c) ? null : c.f|]; } }", @" @@ -714,7 +1371,7 @@ void M(C c) [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] - public async Task TestWithNullableTypeAndReferenceEqualsWithObject2() + public async Task TestWithNullableTypeAndReferenceEqualsWithObject2_IfStatement() { await TestInRegularAndScript1Async( @" @@ -723,7 +1380,8 @@ class C public int? f; void M(C c) { - int? x = [||]object.ReferenceEquals(null, c) ? null : c.f; + [|if|] (!object.ReferenceEquals(null, c)) + c.f?.ToString(); } }", @" @@ -732,7 +1390,7 @@ class C public int? f; void M(C c) { - int? x = c?.f; + c?.f?.ToString(); } }"); } @@ -748,7 +1406,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]object.ReferenceEquals(c, other) ? null : c.f; + int? x = object.ReferenceEquals(c, other) ? null : c.f; } }"); } @@ -764,7 +1422,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]object.ReferenceEquals(other, c) ? null : c.f; + int? x = object.ReferenceEquals(other, c) ? null : c.f; } }"); } @@ -780,7 +1438,32 @@ class C public int? f; void M(C c) { - int? x = [||]!(c is null) ? c.f : null; + int? x = [|!(c is null) ? c.f : null|]; + } +}", +@" +class C +{ + public int? f; + void M(C c) + { + int? x = c?.f; + } +}"); + } + + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestWithNullableTypeAndNotIsNotNull() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + int? x = [|!(c is not null) ? null : c.f|]; } }", @" @@ -805,7 +1488,7 @@ class C public int? f; void M(C c) { - int? x = [||]!(c is C) ? c.f : null; + int? x = !(c is C) ? c.f : null; } }"); } @@ -820,7 +1503,7 @@ class C { void M(string s) { - int? x = [||]!(s is """") ? (int?)s.Length : null; + int? x = !(s is """") ? (int?)s.Length : null; } }"); } @@ -836,7 +1519,7 @@ class C public int? f; void M(C c) { - int? x = [||]!ReferenceEquals(c, null) ? c.f : null; + int? x = [|!ReferenceEquals(c, null) ? c.f : null|]; } }", @" @@ -861,7 +1544,7 @@ class C public int? f; void M(C c) { - int? x = [||]!ReferenceEquals(null, c) ? c.f : null; + int? x = [|!ReferenceEquals(null, c) ? c.f : null|]; } }", @" @@ -886,7 +1569,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]!ReferenceEquals(c, other) ? c.f : null; + int? x = !ReferenceEquals(c, other) ? c.f : null; } }"); } @@ -902,7 +1585,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]!ReferenceEquals(other, c) ? c.f : null; + int? x = !ReferenceEquals(other, c) ? c.f : null; } }"); } @@ -918,7 +1601,7 @@ class C public int? f; void M(C c) { - int? x = [||]!object.ReferenceEquals(c, null) ? c.f : null; + int? x = [|!object.ReferenceEquals(c, null) ? c.f : null|]; } }", @" @@ -943,7 +1626,7 @@ class C public int? f; void M(C c) { - int? x = [||]!object.ReferenceEquals(null, c) ? c.f : null; + int? x = [|!object.ReferenceEquals(null, c) ? c.f : null|]; } }", @" @@ -968,7 +1651,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]!object.ReferenceEquals(c, other) ? c.f : null; + int? x = !object.ReferenceEquals(c, other) ? c.f : null; } }"); } @@ -984,7 +1667,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]!object.ReferenceEquals(other, c) ? c.f : null; + int? x = !object.ReferenceEquals(other, c) ? c.f : null; } }"); } @@ -1000,7 +1683,7 @@ class C public int? f; void M(C c) { - int? x = [||]!(c == null) ? c.f : null; + int? x = [|!(c == null) ? c.f : null|]; } }", @" @@ -1014,6 +1697,32 @@ void M(C c) }"); } + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestEqualsWithLogicalNot_IfStatement() + { + await TestInRegularAndScript1Async( +@" +class C +{ + public int? f; + void M(C c) + { + [|if|] (!(c == null)) + c.f?.ToString(); + } +}", +@" +class C +{ + public int? f; + void M(C c) + { + c?.f?.ToString(); + } +}"); + } + [WorkItem(23043, "https://github.com/dotnet/roslyn/issues/23043")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] public async Task TestNotEqualsWithLogicalNot() @@ -1025,7 +1734,7 @@ class C public int? f; void M(C c) { - int? x = [||]!(c != null) ? null : c.f; + int? x = [|!(c != null) ? null : c.f|]; } }", @" @@ -1050,7 +1759,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]!(c == other) ? c.f : null; + int? x = !(c == other) ? c.f : null; } }"); } @@ -1066,7 +1775,7 @@ class C public int? f; void M(C c, C other) { - int? x = [||]!(c != other) ? null : c.f; + int? x = !(c != other) ? null : c.f; } }"); } @@ -1082,7 +1791,7 @@ class C { void M(object o) { - var v = [||](o == null) ? null : (o.ToString()); + var v = [|(o == null) ? null : (o.ToString())|]; } }", @"using System; @@ -1107,7 +1816,7 @@ class C { void M(object o) { - var v = [||](o != null) ? (o.ToString()) : null; + var v = [|(o != null) ? (o.ToString()) : null|]; } }", @"using System; @@ -1132,7 +1841,7 @@ class C { void M(object o) { - var v = [||]o == null ? (null) : o.ToString(); + var v = [|o == null ? (null) : o.ToString()|]; } }", @"using System; @@ -1157,7 +1866,7 @@ class C { void M(object o) { - var v = [||]o != null ? o.ToString() : (null); + var v = [|o != null ? o.ToString() : (null)|]; } }", @"using System; @@ -1168,6 +1877,171 @@ void M(object o) { var v = o?.ToString(); } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestIfStatement_Trivia1() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + // Before + [|if|] (o != null) + o.ToString(); + } +}", +@"using System; + +class C +{ + void M(object o) + { + // Before + o?.ToString(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestIfStatement_Trivia2() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + // Before1 + [|if|] (o != null) + // Before2 + o.ToString(); + } +}", +@"using System; + +class C +{ + void M(object o) + { + // Before1 + // Before2 + o?.ToString(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestIfStatement_Trivia3() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + // Before1 + [|if|] (o != null) + { + // Before2 + o.ToString(); + } + } +}", +@"using System; + +class C +{ + void M(object o) + { + // Before1 + // Before2 + o?.ToString(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestIfStatement_Trivia4() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + // Before1 + [|if|] (o != null) + { + // Before2 + o.ToString(); // After + } + } +}", +@"using System; + +class C +{ + void M(object o) + { + // Before1 + // Before2 + o?.ToString(); // After + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestIfStatement_Trivia5() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void M(object o) + { + // Before1 + [|if|] (o != null) + { + // Before2 + o.ToString(); + } // After + } +}", +@"using System; + +class C +{ + void M(object o) + { + // Before1 + // Before2 + o?.ToString(); // After + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)] + public async Task TestNotOnPointer_IfStatement() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + unsafe void M(int* i) + { + if (i != null) + i->ToString(); + } }"); } } diff --git a/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs b/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs index e704178d92377..417ab48bce3c8 100644 --- a/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs +++ b/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs @@ -1639,6 +1639,52 @@ void M() // intentionally empty } }", +parseOptions: CSharp8ParseOptions); + } + + [WorkItem(58911, "https://github.com/dotnet/roslyn/issues/58911")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseSimpleUsingStatement)] + public async Task TestUsingWithoutSpace() + { + await TestInRegularAndScriptAsync( +@"public class Test +{ + public IEnumerable Collection { get; } = new[] + { + new Test() + { + Prop = () => + { + [||]using(var x = Get()) + { + int i = 0; + } + } + } + }; + + public Action? Prop { get; set; } + public static IDisposable Get() => throw new NotImplementedException(); +} +", +@"public class Test +{ + public IEnumerable Collection { get; } = new[] + { + new Test() + { + Prop = () => + { + using var x = Get(); + int i = 0; + } + } + }; + + public Action? Prop { get; set; } + public static IDisposable Get() => throw new NotImplementedException(); +} +", parseOptions: CSharp8ParseOptions); } } diff --git a/src/Analyzers/CSharp/Tests/UseUtf8StringLiteral/UseUtf8StringLiteralTests.cs b/src/Analyzers/CSharp/Tests/UseUtf8StringLiteral/UseUtf8StringLiteralTests.cs index a1e66262e9c56..9cf9a90ceb132 100644 --- a/src/Analyzers/CSharp/Tests/UseUtf8StringLiteral/UseUtf8StringLiteralTests.cs +++ b/src/Analyzers/CSharp/Tests/UseUtf8StringLiteral/UseUtf8StringLiteralTests.cs @@ -515,7 +515,7 @@ public class C { public void M() { - var x = [|new|] byte[] { 34, 92, 0, 7, 8, 12, 10, 13, 9, 11 }; + var x = [|new|] byte[] { 34, 92, 10, 13, 9 }; } }", FixedCode = @@ -524,7 +524,7 @@ public class C { public void M() { - var x = ""\""\\\0\a\b\f\n\r\t\v""u8.ToArray(); + var x = ""\""\\\n\r\t""u8.ToArray(); } }", CodeActionValidationMode = CodeActionValidationMode.None, @@ -761,41 +761,8 @@ public void Dispose(int a = 1, bool b = true, params byte[] others) { } } [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseUtf8StringLiteral)] - // Various cases copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs - [InlineData(new byte[] { 0x37, 0x02, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65 }, "7translate")] - [InlineData(new byte[] { 0x3f, 0x01 }, "?")] - public async Task TestValidUtf8Strings(byte[] bytes, string stringValue) - { - await new VerifyCS.Test - { - TestCode = -$@" -public class C -{{ - private static readonly byte[] _bytes = [|new|] byte[] {{ {string.Join(", ", bytes)} }}; -}} -", - FixedCode = -$@" -public class C -{{ - private static readonly byte[] _bytes = ""{stringValue}""u8.ToArray(); -}} -", - CodeActionValidationMode = CodeActionValidationMode.None, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60, - LanguageVersion = LanguageVersion.Preview - }.RunAsync(); - - // Lets make sure there aren't any false positives here, and make sure the byte array actually - // correctly round-trips via UTF-8 - var newStringValue = Encoding.UTF8.GetString(bytes); - Assert.Equal(stringValue, newStringValue); - var newBytes = Encoding.UTF8.GetBytes(stringValue); - Assert.Equal(bytes, newBytes); - } - - [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseUtf8StringLiteral)] + // Standard C# escape characters + [InlineData(new byte[] { 0, 7, 8, 12, 11 })] // Various cases copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http2/HuffmanDecodingTests.cs [InlineData(new byte[] { 0xff, 0xcf })] [InlineData(new byte[] { 0b100111_00, 0b101_10100, 0b0_101000_0, 0b0111_1111 })] @@ -803,8 +770,12 @@ public class C [InlineData(new byte[] { 0xfe, 0x53 })] [InlineData(new byte[] { 0xff, 0xff, 0xf6, 0xff, 0xff, 0xfd, 0x68 })] [InlineData(new byte[] { 0xff, 0xff, 0xf9, 0xff, 0xff, 0xfd, 0x86 })] - // _headerNameHuffmanBytes from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs + // Various cases copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/Tests/System/Net/aspnetcore/Http3/QPackDecoderTest.cs [InlineData(new byte[] { 0xa8, 0xbe, 0x16, 0x9c, 0xa3, 0x90, 0xb6, 0x7f })] + [InlineData(new byte[] { 0x37, 0x02, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65 })] + [InlineData(new byte[] { 0x3f, 0x01 })] + // DaysInMonth365 from https://github.com/dotnet/runtime/blob/b5a8ece073110140e2d9696cdfdc047ec78c2fa1/src/libraries/System.Private.CoreLib/src/System/DateTime.cs + [InlineData(new byte[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 })] public async Task TestInvalidUtf8Strings(byte[] bytes) { await new VerifyCS.Test @@ -820,12 +791,61 @@ public class C ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview }.RunAsync(); + } - // Lets make sure there aren't any false negatives here, and see if the byte array would actually - // correctly round-trip via UTF-8 - var stringValue = Encoding.UTF8.GetString(bytes); - var newBytes = Encoding.UTF8.GetBytes(stringValue); - Assert.NotEqual(bytes, newBytes); + [Fact] + public async Task TestDoesNotOfferForControlCharacters() + { + // Copied from https://github.com/dotnet/runtime/blob/6a889d234267a4c96ed21d0e1660dce787d78a38/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Conversion.cs + var input = """ + class C + { + internal enum ConvKind + { + Identity = 1, // Identity conversion + Implicit = 2, // Implicit conversion + Explicit = 3, // Explicit conversion + Unknown = 4, // Unknown so call canConvert + None = 5, // None + } + + private const byte ID = (byte)ConvKind.Identity; // 0x01 + private const byte IMP = (byte)ConvKind.Implicit; // 0x02 + private const byte EXP = (byte)ConvKind.Explicit; // 0x03 + private const byte NO = (byte)ConvKind.None; // 0x05 + private const byte CONV_KIND_MASK = 0x0F; + private const byte UDC = 0x40; + private const byte XUD = EXP | UDC; + private const byte IUD = IMP | UDC; + + private static readonly byte[][] s_simpleTypeConversions = + { + // to: BYTE I2 I4 I8 FLT DBL DEC CHAR BOOL SBYTE U2 U4 U8 + /* from */ + new byte[] /* BYTE */ { ID, IMP, IMP, IMP, IMP, IMP, IUD, EXP, NO, EXP, IMP, IMP, IMP }, + new byte[] /* I2 */ { EXP, ID, IMP, IMP, IMP, IMP, IUD, EXP, NO, EXP, EXP, EXP, EXP }, + new byte[] /* I4 */ { EXP, EXP, ID, IMP, IMP, IMP, IUD, EXP, NO, EXP, EXP, EXP, EXP }, + new byte[] /* I8 */ { EXP, EXP, EXP, ID, IMP, IMP, IUD, EXP, NO, EXP, EXP, EXP, EXP }, + new byte[] /* FLT */ { EXP, EXP, EXP, EXP, ID, IMP, XUD, EXP, NO, EXP, EXP, EXP, EXP }, + new byte[] /* DBL */ { EXP, EXP, EXP, EXP, EXP, ID, XUD, EXP, NO, EXP, EXP, EXP, EXP }, + new byte[] /* DEC */ { XUD, XUD, XUD, XUD, XUD, XUD, ID, XUD, NO, XUD, XUD, XUD, XUD }, + new byte[] /* CHAR */ { EXP, EXP, IMP, IMP, IMP, IMP, IUD, ID, NO, EXP, IMP, IMP, IMP }, + new byte[] /* BOOL */ { NO, NO, NO, NO, NO, NO, NO, NO, ID, NO, NO, NO, NO }, + new byte[] /*SBYTE */ { EXP, IMP, IMP, IMP, IMP, IMP, IUD, EXP, NO, ID, EXP, EXP, EXP }, + new byte[] /* U2 */ { EXP, EXP, IMP, IMP, IMP, IMP, IUD, EXP, NO, EXP, ID, IMP, IMP }, + new byte[] /* U4 */ { EXP, EXP, EXP, IMP, IMP, IMP, IUD, EXP, NO, EXP, EXP, ID, IMP }, + new byte[] /* U8 */ { EXP, EXP, EXP, EXP, IMP, IMP, IUD, EXP, NO, EXP, EXP, EXP, ID }, + }; + } + """; + + await new VerifyCS.Test + { + TestCode = input, + CodeActionValidationMode = CodeActionValidationMode.None, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = LanguageVersion.Preview + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseUtf8StringLiteral)] @@ -1466,5 +1486,71 @@ public void M(byte[][] i, byte[] b) LanguageVersion = LanguageVersion.Preview }.RunAsync(); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseUtf8StringLiteral)] + public async Task TestTargettingReadOnlySpan1() + { + await new VerifyCS.Test + { + TestCode = +@" +using System; + +public class C +{ + public void M() + { + ReadOnlySpan x = [|new|] byte[] { 65, 66, 67 }; + } +}", + FixedCode = +@" +using System; + +public class C +{ + public void M() + { + ReadOnlySpan x = ""ABC""u8; + } +}", + CodeActionValidationMode = CodeActionValidationMode.None, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = LanguageVersion.Preview + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseUtf8StringLiteral)] + public async Task TestTargettingReadOnlySpan2() + { + await new VerifyCS.Test + { + TestCode = +@" +using System; + +public class C +{ + public void M(ReadOnlySpan x) + { + M(/* 1 */[|new|] byte[] { 65, 66, 67 }/* 2 */); + } +}", + FixedCode = +@" +using System; + +public class C +{ + public void M(ReadOnlySpan x) + { + M(/* 1 */""ABC""u8/* 2 */); + } +}", + CodeActionValidationMode = CodeActionValidationMode.None, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = LanguageVersion.Preview + }.RunAsync(); + } } } diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems index 02e058b758a0e..35700eeba363f 100644 --- a/src/Analyzers/Core/Analyzers/Analyzers.projitems +++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems @@ -88,6 +88,7 @@ + diff --git a/src/Analyzers/Core/Analyzers/Helpers/DiagnosticHelper.cs b/src/Analyzers/Core/Analyzers/Helpers/DiagnosticHelper.cs index 36d5d9a48fb88..02f58e3f3aa32 100644 --- a/src/Analyzers/Core/Analyzers/Helpers/DiagnosticHelper.cs +++ b/src/Analyzers/Core/Analyzers/Helpers/DiagnosticHelper.cs @@ -139,7 +139,7 @@ public static Diagnostic CreateWithLocationTags( { if (additionalUnnecessaryLocations.IsEmpty) { - return Create(descriptor, location, effectiveSeverity, additionalLocations, ImmutableDictionary.Empty, messageArgs); + return Create(descriptor, location, effectiveSeverity, additionalLocations, properties, messageArgs); } var tagIndices = ImmutableDictionary>.Empty diff --git a/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs index ed27e55f04ede..d668af8faffd5 100644 --- a/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs @@ -51,6 +51,12 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() private void AnalyzeNamespaceNode(SyntaxNodeAnalysisContext context) { + var option = context.GetAnalyzerOptions().PreferNamespaceAndFolderMatchStructure; + if (!option.Value) + { + return; + } + // It's ok to not have a rootnamespace property, but if it's there we want to use it correctly context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue(MatchFolderAndNamespaceConstants.RootNamespaceOption, out var rootNamespace); diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs index 54cd8810a9382..50d34cba2d253 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs @@ -420,7 +420,7 @@ private bool ShouldAnalyze(IOperation operationBlock, ISymbol owningSymbol, ref return false; default: - // Workaround for https://github.com/dotnet/roslyn/issues/32100 + // Workaround for https://github.com/dotnet/roslyn/issues/27564 // Bail out in presence of OperationKind.None - not implemented IOperation. if (operation.Kind == OperationKind.None) { diff --git a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs index 611fb8b279530..5531c936d36aa 100644 --- a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs @@ -18,24 +18,38 @@ internal static class UseNullPropagationConstants public const string WhenPartIsNullable = nameof(WhenPartIsNullable); } - internal abstract class AbstractUseNullPropagationDiagnosticAnalyzer< + /// + /// Looks for code snippets similar to x == null ? null : x.Y() and converts it to x?.Y(). This form is also supported: + /// + /// if (x != null) + /// x.Y(); + /// + /// + internal abstract partial class AbstractUseNullPropagationDiagnosticAnalyzer< TSyntaxKind, TExpressionSyntax, + TStatementSyntax, TConditionalExpressionSyntax, TBinaryExpressionSyntax, - TInvocationExpression, - TMemberAccessExpression, - TConditionalAccessExpression, - TElementAccessExpression> : AbstractBuiltInCodeStyleDiagnosticAnalyzer + TInvocationExpressionSyntax, + TConditionalAccessExpressionSyntax, + TElementAccessExpressionSyntax, + TIfStatementSyntax, + TExpressionStatementSyntax> : AbstractBuiltInCodeStyleDiagnosticAnalyzer where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode where TConditionalExpressionSyntax : TExpressionSyntax where TBinaryExpressionSyntax : TExpressionSyntax - where TInvocationExpression : TExpressionSyntax - where TMemberAccessExpression : TExpressionSyntax - where TConditionalAccessExpression : TExpressionSyntax - where TElementAccessExpression : TExpressionSyntax + where TInvocationExpressionSyntax : TExpressionSyntax + where TConditionalAccessExpressionSyntax : TExpressionSyntax + where TElementAccessExpressionSyntax : TExpressionSyntax + where TIfStatementSyntax : TStatementSyntax + where TExpressionStatementSyntax : TStatementSyntax { + private static readonly ImmutableDictionary s_whenPartIsNullableProperties = + ImmutableDictionary.Empty.Add(UseNullPropagationConstants.WhenPartIsNullable, ""); + protected AbstractUseNullPropagationDiagnosticAnalyzer() : base(IDEDiagnosticIds.UseNullPropagationDiagnosticId, EnforceOnBuildValues.UseNullPropagation, @@ -50,104 +64,90 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() protected abstract bool ShouldAnalyze(Compilation compilation); + protected abstract TSyntaxKind IfStatementSyntaxKind { get; } protected abstract ISyntaxFacts GetSyntaxFacts(); protected abstract bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol? expressionTypeOpt, CancellationToken cancellationToken); protected abstract bool TryAnalyzePatternCondition( - ISyntaxFacts syntaxFacts, SyntaxNode conditionNode, - [NotNullWhen(true)] out SyntaxNode? conditionPartToCheck, out bool isEquals); + ISyntaxFacts syntaxFacts, TExpressionSyntax conditionNode, + [NotNullWhen(true)] out TExpressionSyntax? conditionPartToCheck, out bool isEquals); protected override void InitializeWorker(AnalysisContext context) { - context.RegisterCompilationStartAction(startContext => + context.RegisterCompilationStartAction(context => { - if (!ShouldAnalyze(startContext.Compilation)) - { + if (!ShouldAnalyze(context.Compilation)) return; - } - var expressionTypeOpt = startContext.Compilation.ExpressionOfTType(); + var expressionType = context.Compilation.ExpressionOfTType(); - var objectType = startContext.Compilation.GetSpecialType(SpecialType.System_Object); - var referenceEqualsMethodOpt = objectType?.GetMembers(nameof(ReferenceEquals)) + var objectType = context.Compilation.GetSpecialType(SpecialType.System_Object); + var referenceEqualsMethod = objectType?.GetMembers(nameof(ReferenceEquals)) .OfType() .FirstOrDefault(m => m.DeclaredAccessibility == Accessibility.Public && m.Parameters.Length == 2); var syntaxKinds = GetSyntaxFacts().SyntaxKinds; - startContext.RegisterSyntaxNodeAction( - c => AnalyzeSyntax(c, expressionTypeOpt, referenceEqualsMethodOpt), + context.RegisterSyntaxNodeAction( + context => AnalyzeTernaryConditionalExpression(context, expressionType, referenceEqualsMethod), syntaxKinds.Convert(syntaxKinds.TernaryConditionalExpression)); + context.RegisterSyntaxNodeAction( + context => AnalyzeIfStatement(context, referenceEqualsMethod), + IfStatementSyntaxKind); }); - } - private void AnalyzeSyntax( + private void AnalyzeTernaryConditionalExpression( SyntaxNodeAnalysisContext context, - INamedTypeSymbol? expressionTypeOpt, - IMethodSymbol? referenceEqualsMethodOpt) + INamedTypeSymbol? expressionType, + IMethodSymbol? referenceEqualsMethod) { + var cancellationToken = context.CancellationToken; var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var option = context.GetAnalyzerOptions().PreferNullPropagation; if (!option.Value) - { return; - } var syntaxFacts = GetSyntaxFacts(); syntaxFacts.GetPartsOfConditionalExpression( - conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode); + conditionalExpression, out var condition, out var whenTrue, out var whenFalse); - conditionNode = syntaxFacts.WalkDownParentheses(conditionNode); - whenTrueNode = syntaxFacts.WalkDownParentheses(whenTrueNode); - whenFalseNode = syntaxFacts.WalkDownParentheses(whenFalseNode); + var conditionNode = (TExpressionSyntax)condition; - var conditionIsNegated = false; - if (syntaxFacts.IsLogicalNotExpression(conditionNode)) - { - conditionIsNegated = true; - conditionNode = syntaxFacts.WalkDownParentheses( - syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode)); - } + var whenTrueNode = (TExpressionSyntax)syntaxFacts.WalkDownParentheses(whenTrue); + var whenFalseNode = (TExpressionSyntax)syntaxFacts.WalkDownParentheses(whenFalse); if (!TryAnalyzeCondition( - context, syntaxFacts, referenceEqualsMethodOpt, conditionNode, + context, syntaxFacts, referenceEqualsMethod, conditionNode, out var conditionPartToCheck, out var isEquals)) { return; } - if (conditionIsNegated) - { - isEquals = !isEquals; - } - // Needs to be of the form: // x == null ? null : ... or // x != null ? ... : null; if (isEquals && !syntaxFacts.IsNullLiteralExpression(whenTrueNode)) - { return; - } if (!isEquals && !syntaxFacts.IsNullLiteralExpression(whenFalseNode)) - { return; - } var whenPartToCheck = isEquals ? whenFalseNode : whenTrueNode; var semanticModel = context.SemanticModel; - var whenPartMatch = GetWhenPartMatch(syntaxFacts, semanticModel, conditionPartToCheck, whenPartToCheck); + var whenPartMatch = GetWhenPartMatch(syntaxFacts, semanticModel, conditionPartToCheck, whenPartToCheck, cancellationToken); if (whenPartMatch == null) - { return; - } - // ?. is not available in expression-trees. Disallow the fix in that case. + // can't use ?. on a pointer + var whenPartType = semanticModel.GetTypeInfo(whenPartMatch, cancellationToken).Type; + if (whenPartType is IPointerTypeSymbol) + return; - var type = semanticModel.GetTypeInfo(conditionalExpression).Type; + // ?. is not available in expression-trees. Disallow the fix in that case. + var type = semanticModel.GetTypeInfo(conditionalExpression, cancellationToken).Type; if (type?.IsValueType == true) { if (type is not INamedTypeSymbol namedType || namedType.ConstructedFrom.SpecialType != SpecialType.System_Nullable_T) @@ -161,22 +161,18 @@ private void AnalyzeSyntax( // converting to c?.nullable doesn't affect the type } - if (IsInExpressionTree(semanticModel, conditionNode, expressionTypeOpt, context.CancellationToken)) - { + if (IsInExpressionTree(semanticModel, conditionNode, expressionType, cancellationToken)) return; - } var locations = ImmutableArray.Create( conditionalExpression.GetLocation(), conditionPartToCheck.GetLocation(), whenPartToCheck.GetLocation()); - var properties = ImmutableDictionary.Empty; - var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; - if (whenPartIsNullable) - { - properties = properties.Add(UseNullPropagationConstants.WhenPartIsNullable, ""); - } + var whenPartIsNullable = whenPartType?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + var properties = whenPartIsNullable + ? s_whenPartIsNullableProperties + : ImmutableDictionary.Empty; context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, @@ -189,31 +185,41 @@ private void AnalyzeSyntax( private bool TryAnalyzeCondition( SyntaxNodeAnalysisContext context, ISyntaxFacts syntaxFacts, - IMethodSymbol? referenceEqualsMethodOpt, - SyntaxNode conditionNode, - [NotNullWhen(true)] out SyntaxNode? conditionPartToCheck, + IMethodSymbol? referenceEqualsMethod, + TExpressionSyntax condition, + [NotNullWhen(true)] out TExpressionSyntax? conditionPartToCheck, out bool isEquals) { - switch (conditionNode) + condition = (TExpressionSyntax)syntaxFacts.WalkDownParentheses(condition); + var conditionIsNegated = false; + if (syntaxFacts.IsLogicalNotExpression(condition)) { - case TBinaryExpressionSyntax binaryExpression: - return TryAnalyzeBinaryExpressionCondition( - syntaxFacts, binaryExpression, out conditionPartToCheck, out isEquals); - - case TInvocationExpression invocation: - return TryAnalyzeInvocationCondition( - context, syntaxFacts, referenceEqualsMethodOpt, invocation, - out conditionPartToCheck, out isEquals); - - default: - return TryAnalyzePatternCondition( - syntaxFacts, conditionNode, out conditionPartToCheck, out isEquals); + conditionIsNegated = true; + condition = (TExpressionSyntax)syntaxFacts.WalkDownParentheses( + syntaxFacts.GetOperandOfPrefixUnaryExpression(condition)); } + + var result = condition switch + { + TBinaryExpressionSyntax binaryExpression => TryAnalyzeBinaryExpressionCondition( + syntaxFacts, binaryExpression, out conditionPartToCheck, out isEquals), + + TInvocationExpressionSyntax invocation => TryAnalyzeInvocationCondition( + context, syntaxFacts, referenceEqualsMethod, invocation, + out conditionPartToCheck, out isEquals), + + _ => TryAnalyzePatternCondition(syntaxFacts, condition, out conditionPartToCheck, out isEquals), + }; + + if (conditionIsNegated) + isEquals = !isEquals; + + return result; } private static bool TryAnalyzeBinaryExpressionCondition( ISyntaxFacts syntaxFacts, TBinaryExpressionSyntax condition, - [NotNullWhen(true)] out SyntaxNode? conditionPartToCheck, out bool isEquals) + [NotNullWhen(true)] out TExpressionSyntax? conditionPartToCheck, out bool isEquals) { var syntaxKinds = syntaxFacts.SyntaxKinds; isEquals = syntaxKinds.ReferenceEqualsExpression == condition.RawKind; @@ -226,7 +232,7 @@ private static bool TryAnalyzeBinaryExpressionCondition( else { syntaxFacts.GetPartsOfBinaryExpression(condition, out var conditionLeft, out var conditionRight); - conditionPartToCheck = GetConditionPartToCheck(syntaxFacts, conditionLeft, conditionRight); + conditionPartToCheck = GetConditionPartToCheck(syntaxFacts, (TExpressionSyntax)conditionLeft, (TExpressionSyntax)conditionRight); return conditionPartToCheck != null; } } @@ -234,14 +240,17 @@ private static bool TryAnalyzeBinaryExpressionCondition( private static bool TryAnalyzeInvocationCondition( SyntaxNodeAnalysisContext context, ISyntaxFacts syntaxFacts, - IMethodSymbol? referenceEqualsMethodOpt, - TInvocationExpression invocation, - [NotNullWhen(true)] out SyntaxNode? conditionPartToCheck, + IMethodSymbol? referenceEqualsMethod, + TInvocationExpressionSyntax invocation, + [NotNullWhen(true)] out TExpressionSyntax? conditionPartToCheck, out bool isEquals) { conditionPartToCheck = null; isEquals = true; + if (referenceEqualsMethod == null) + return false; + var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocation); var nameNode = syntaxFacts.IsIdentifierName(expression) ? expression @@ -266,8 +275,8 @@ private static bool TryAnalyzeInvocationCondition( return false; } - var conditionLeft = syntaxFacts.GetExpressionOfArgument(arguments[0]); - var conditionRight = syntaxFacts.GetExpressionOfArgument(arguments[1]); + var conditionLeft = (TExpressionSyntax)syntaxFacts.GetExpressionOfArgument(arguments[0]); + var conditionRight = (TExpressionSyntax)syntaxFacts.GetExpressionOfArgument(arguments[1]); if (conditionLeft == null || conditionRight == null) { return false; @@ -282,10 +291,11 @@ private static bool TryAnalyzeInvocationCondition( var semanticModel = context.SemanticModel; var cancellationToken = context.CancellationToken; var symbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol; - return referenceEqualsMethodOpt != null && referenceEqualsMethodOpt.Equals(symbol); + return referenceEqualsMethod.Equals(symbol); } - private static SyntaxNode? GetConditionPartToCheck(ISyntaxFacts syntaxFacts, SyntaxNode conditionLeft, SyntaxNode conditionRight) + private static TExpressionSyntax? GetConditionPartToCheck( + ISyntaxFacts syntaxFacts, TExpressionSyntax conditionLeft, TExpressionSyntax conditionRight) { var conditionLeftIsNull = syntaxFacts.IsNullLiteralExpression(conditionLeft); var conditionRightIsNull = syntaxFacts.IsNullLiteralExpression(conditionRight); @@ -304,71 +314,61 @@ private static bool TryAnalyzeInvocationCondition( return conditionRightIsNull ? conditionLeft : conditionRight; } - internal static SyntaxNode? GetWhenPartMatch( - ISyntaxFacts syntaxFacts, SemanticModel semanticModel, SyntaxNode expressionToMatch, SyntaxNode whenPart) + internal static TExpressionSyntax? GetWhenPartMatch( + ISyntaxFacts syntaxFacts, + SemanticModel semanticModel, + TExpressionSyntax expressionToMatch, + TExpressionSyntax whenPart, + CancellationToken cancellationToken) { - expressionToMatch = RemoveObjectCastIfAny(syntaxFacts, semanticModel, expressionToMatch); + expressionToMatch = RemoveObjectCastIfAny(syntaxFacts, semanticModel, expressionToMatch, cancellationToken); var current = whenPart; while (true) { var unwrapped = Unwrap(syntaxFacts, current); if (unwrapped == null) - { return null; - } - if (current is TMemberAccessExpression or - TElementAccessExpression) + if (syntaxFacts.IsSimpleMemberAccessExpression(current) || current is TElementAccessExpressionSyntax) { if (syntaxFacts.AreEquivalent(unwrapped, expressionToMatch)) - { return unwrapped; - } } current = unwrapped; } } - private static SyntaxNode RemoveObjectCastIfAny(ISyntaxFacts syntaxFacts, SemanticModel semanticModel, SyntaxNode node) + private static TExpressionSyntax RemoveObjectCastIfAny( + ISyntaxFacts syntaxFacts, SemanticModel semanticModel, TExpressionSyntax node, CancellationToken cancellationToken) { if (syntaxFacts.IsCastExpression(node)) { syntaxFacts.GetPartsOfCastExpression(node, out var type, out var expression); - var typeSymbol = semanticModel.GetTypeInfo(type).Type; + var typeSymbol = semanticModel.GetTypeInfo(type, cancellationToken).Type; if (typeSymbol?.SpecialType == SpecialType.System_Object) - { - return expression; - } + return (TExpressionSyntax)expression; } return node; } - private static SyntaxNode? Unwrap(ISyntaxFacts syntaxFacts, SyntaxNode node) + private static TExpressionSyntax? Unwrap(ISyntaxFacts syntaxFacts, TExpressionSyntax node) { - node = syntaxFacts.WalkDownParentheses(node); + node = (TExpressionSyntax)syntaxFacts.WalkDownParentheses(node); - if (node is TInvocationExpression invocation) - { - return syntaxFacts.GetExpressionOfInvocationExpression(invocation); - } + if (node is TInvocationExpressionSyntax invocation) + return (TExpressionSyntax)syntaxFacts.GetExpressionOfInvocationExpression(invocation); - if (node is TMemberAccessExpression memberAccess) - { - return syntaxFacts.GetExpressionOfMemberAccessExpression(memberAccess); - } + if (syntaxFacts.IsSimpleMemberAccessExpression(node)) + return (TExpressionSyntax?)syntaxFacts.GetExpressionOfMemberAccessExpression(node); - if (node is TConditionalAccessExpression conditionalAccess) - { - return syntaxFacts.GetExpressionOfConditionalAccessExpression(conditionalAccess); - } + if (node is TConditionalAccessExpressionSyntax conditionalAccess) + return (TExpressionSyntax)syntaxFacts.GetExpressionOfConditionalAccessExpression(conditionalAccess); - if (node is TElementAccessExpression elementAccess) - { - return syntaxFacts.GetExpressionOfElementAccessExpression(elementAccess); - } + if (node is TElementAccessExpressionSyntax elementAccess) + return (TExpressionSyntax?)syntaxFacts.GetExpressionOfElementAccessExpression(elementAccess); return null; } diff --git a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs new file mode 100644 index 0000000000000..f0de080619f36 --- /dev/null +++ b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs @@ -0,0 +1,82 @@ +// 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.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.UseNullPropagation; + +internal abstract partial class AbstractUseNullPropagationDiagnosticAnalyzer< + TSyntaxKind, + TExpressionSyntax, + TStatementSyntax, + TConditionalExpressionSyntax, + TBinaryExpressionSyntax, + TInvocationExpressionSyntax, + TConditionalAccessExpressionSyntax, + TElementAccessExpressionSyntax, + TIfStatementSyntax, + TExpressionStatementSyntax> +{ + protected abstract bool TryGetPartsOfIfStatement( + TIfStatementSyntax ifStatement, [NotNullWhen(true)] out TExpressionSyntax? condition, [NotNullWhen(true)] out TStatementSyntax? trueStatement); + + private void AnalyzeIfStatement( + SyntaxNodeAnalysisContext context, + IMethodSymbol? referenceEqualsMethod) + { + var cancellationToken = context.CancellationToken; + var option = context.GetAnalyzerOptions().PreferNullPropagation; + if (!option.Value) + return; + + var syntaxFacts = GetSyntaxFacts(); + var ifStatement = (TIfStatementSyntax)context.Node; + + // The true-statement if the if-statement has to be a statement of the form `.Name(...)`; + if (!TryGetPartsOfIfStatement(ifStatement, out var condition, out var trueStatement)) + return; + + if (trueStatement is not TExpressionStatementSyntax expressionStatement) + return; + + // Now see if the `if ()` looks like an appropriate null check. + if (!TryAnalyzeCondition(context, syntaxFacts, referenceEqualsMethod, condition, out var conditionPartToCheck, out var isEquals)) + return; + + // Ok, we have `if ( == null)` or `if ( != null)` (or some similar form of that. `conditionPartToCheck` will be `` here. + // We only support `if ( != null)`. Fail out if we have the alternate form. + if (isEquals) + return; + + var semanticModel = context.SemanticModel; + var whenPartMatch = GetWhenPartMatch( + syntaxFacts, semanticModel, conditionPartToCheck, + (TExpressionSyntax)syntaxFacts.GetExpressionOfExpressionStatement(expressionStatement), + cancellationToken); + if (whenPartMatch == null) + return; + + // can't use ?. on a pointer + var whenPartType = semanticModel.GetTypeInfo(whenPartMatch, cancellationToken).Type; + if (whenPartType is IPointerTypeSymbol) + return; + + var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + var properties = whenPartIsNullable + ? s_whenPartIsNullableProperties + : ImmutableDictionary.Empty; + + context.ReportDiagnostic(DiagnosticHelper.Create( + Descriptor, + ifStatement.GetFirstToken().GetLocation(), + option.Notification.Severity, + ImmutableArray.Create( + ifStatement.GetLocation(), + trueStatement.GetLocation(), + whenPartMatch.GetLocation()), + properties)); + } +} diff --git a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs index 607571185ad3d..4428243f15a8b 100644 --- a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -21,26 +22,30 @@ namespace Microsoft.CodeAnalysis.UseNullPropagation internal abstract class AbstractUseNullPropagationCodeFixProvider< TSyntaxKind, TExpressionSyntax, + TStatementSyntax, TConditionalExpressionSyntax, TBinaryExpressionSyntax, - TInvocationExpression, - TMemberAccessExpression, - TConditionalAccessExpression, - TElementAccessExpression, - TElementBindingExpression, - TElementBindingArgumentList> : SyntaxEditorBasedCodeFixProvider + TInvocationExpressionSyntax, + TConditionalAccessExpressionSyntax, + TElementAccessExpressionSyntax, + TElementBindingExpressionSyntax, + TIfStatementSyntax, + TExpressionStatementSyntax, + TElementBindingArgumentListSyntax> : SyntaxEditorBasedCodeFixProvider where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode where TConditionalExpressionSyntax : TExpressionSyntax where TBinaryExpressionSyntax : TExpressionSyntax - where TInvocationExpression : TExpressionSyntax - where TMemberAccessExpression : TExpressionSyntax - where TConditionalAccessExpression : TExpressionSyntax - where TElementAccessExpression : TExpressionSyntax - where TElementBindingExpression : TExpressionSyntax - where TElementBindingArgumentList : SyntaxNode + where TInvocationExpressionSyntax : TExpressionSyntax + where TConditionalAccessExpressionSyntax : TExpressionSyntax + where TElementAccessExpressionSyntax : TExpressionSyntax + where TElementBindingExpressionSyntax : TExpressionSyntax + where TIfStatementSyntax : TStatementSyntax + where TExpressionStatementSyntax : TStatementSyntax + where TElementBindingArgumentListSyntax : SyntaxNode { - protected abstract TElementBindingExpression ElementBindingExpression(TElementBindingArgumentList argumentList); + protected abstract TElementBindingExpressionSyntax ElementBindingExpression(TElementBindingArgumentListSyntax argumentList); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.UseNullPropagationDiagnosticId); @@ -58,56 +63,125 @@ protected override async Task FixAllAsync( Document document, ImmutableArray diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - var syntaxFacts = document.GetRequiredLanguageService(); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var generator = document.GetRequiredLanguageService(); var root = editor.OriginalRoot; foreach (var diagnostic in diagnostics) { - var conditionalExpression = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); - var conditionalPart = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan, getInnermostNodeForTie: true); - var whenPart = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan, getInnermostNodeForTie: true); - syntaxFacts.GetPartsOfConditionalExpression( - conditionalExpression, out var condition, out var whenTrue, out var whenFalse); - whenTrue = syntaxFacts.WalkDownParentheses(whenTrue); - whenFalse = syntaxFacts.WalkDownParentheses(whenFalse); - - var whenPartIsNullable = diagnostic.Properties.ContainsKey(UseNullPropagationConstants.WhenPartIsNullable); - editor.ReplaceNode(conditionalExpression, - (c, _) => + var conditionalExpressionOrIfStatement = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); + if (conditionalExpressionOrIfStatement is TIfStatementSyntax ifStatement) + { + FixIfStatement(document, editor, diagnostic, ifStatement); + } + else + { + FixConditionalExpression(document, editor, semanticModel, diagnostic, conditionalExpressionOrIfStatement, cancellationToken); + } + } + } + + private void FixConditionalExpression( + Document document, + SyntaxEditor editor, + SemanticModel semanticModel, + Diagnostic diagnostic, + SyntaxNode conditionalExpression, + CancellationToken cancellationToken) + { + var root = editor.OriginalRoot; + + var syntaxFacts = document.GetRequiredLanguageService(); + var generator = document.GetRequiredLanguageService(); + + var conditionalPart = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan, getInnermostNodeForTie: true); + var whenPart = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan, getInnermostNodeForTie: true); + syntaxFacts.GetPartsOfConditionalExpression( + conditionalExpression, out var condition, out var whenTrue, out var whenFalse); + whenTrue = syntaxFacts.WalkDownParentheses(whenTrue); + whenFalse = syntaxFacts.WalkDownParentheses(whenFalse); + + var whenPartIsNullable = diagnostic.Properties.ContainsKey(UseNullPropagationConstants.WhenPartIsNullable); + editor.ReplaceNode( + conditionalExpression, + (conditionalExpression, _) => + { + syntaxFacts.GetPartsOfConditionalExpression( + conditionalExpression, out var currentCondition, out var currentWhenTrue, out var currentWhenFalse); + + var currentWhenPartToCheck = whenPart == whenTrue ? currentWhenTrue : currentWhenFalse; + + var match = AbstractUseNullPropagationDiagnosticAnalyzer< + TSyntaxKind, TExpressionSyntax, TStatementSyntax, + TConditionalExpressionSyntax, TBinaryExpressionSyntax, TInvocationExpressionSyntax, + TConditionalAccessExpressionSyntax, TElementAccessExpressionSyntax, + TIfStatementSyntax, TExpressionStatementSyntax>.GetWhenPartMatch( + syntaxFacts, semanticModel, (TExpressionSyntax)conditionalPart, (TExpressionSyntax)currentWhenPartToCheck, cancellationToken); + if (match == null) { - syntaxFacts.GetPartsOfConditionalExpression( - c, out var currentCondition, out var currentWhenTrue, out var currentWhenFalse); - - var currentWhenPartToCheck = whenPart == whenTrue ? currentWhenTrue : currentWhenFalse; - - var match = AbstractUseNullPropagationDiagnosticAnalyzer< - TSyntaxKind, TExpressionSyntax, TConditionalExpressionSyntax, - TBinaryExpressionSyntax, TInvocationExpression, TMemberAccessExpression, - TConditionalAccessExpression, TElementAccessExpression>.GetWhenPartMatch(syntaxFacts, semanticModel!, conditionalPart, currentWhenPartToCheck); - if (match == null) - { - return c; - } - - var newNode = CreateConditionalAccessExpression( - syntaxFacts, generator, whenPartIsNullable, currentWhenPartToCheck, match, c); - - newNode = newNode.WithTriviaFrom(c); - return newNode; - }); + return conditionalExpression; + } + + var newNode = CreateConditionalAccessExpression( + syntaxFacts, generator, whenPartIsNullable, currentWhenPartToCheck, match) ?? conditionalExpression; + + newNode = newNode.WithTriviaFrom(conditionalExpression); + return newNode; + }); + } + + private void FixIfStatement( + Document document, + SyntaxEditor editor, + Diagnostic diagnostic, + TIfStatementSyntax ifStatement) + { + var root = editor.OriginalRoot; + + var syntaxFacts = document.GetRequiredLanguageService(); + var generator = document.GetRequiredLanguageService(); + + var whenTrueStatement = (TExpressionStatementSyntax)root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan); + var match = (TExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan, getInnermostNodeForTie: true); + + var whenPartIsNullable = diagnostic.Properties.ContainsKey(UseNullPropagationConstants.WhenPartIsNullable); + + // we have `if (x != null) x.Y();`. Update `x.Y()` to be `x?.Y()`, then replace the entire + // if-statement with that expression statement. + var newWhenTrueStatement = CreateConditionalAccessExpression( + syntaxFacts, generator, whenPartIsNullable, whenTrueStatement, match); + Contract.ThrowIfNull(newWhenTrueStatement); + + // If there's leading trivia on the original inner statement, then combine that with the leading + // trivia on the if-statement. We'll need to add a formatting annotation so that the leading comments + // are put in the right location. + if (newWhenTrueStatement.GetLeadingTrivia().Any(syntaxFacts.IsRegularComment)) + { + newWhenTrueStatement = newWhenTrueStatement + .WithPrependedLeadingTrivia(ifStatement.GetLeadingTrivia()) + .WithAdditionalAnnotations(Formatter.Annotation); } + else + { + newWhenTrueStatement = newWhenTrueStatement.WithLeadingTrivia(ifStatement.GetLeadingTrivia()); + } + + // If there's trailing comments on the original inner statement, then preserve that. Otherwise, + // replace it with the trailing trivia of hte original if-statement. + if (!newWhenTrueStatement.GetTrailingTrivia().Any(syntaxFacts.IsRegularComment)) + newWhenTrueStatement = newWhenTrueStatement.WithTrailingTrivia(ifStatement.GetTrailingTrivia()); + + editor.ReplaceNode(ifStatement, newWhenTrueStatement); } - private SyntaxNode CreateConditionalAccessExpression( + private TContainer? CreateConditionalAccessExpression( ISyntaxFactsService syntaxFacts, SyntaxGeneratorInternal generator, bool whenPartIsNullable, - SyntaxNode whenPart, SyntaxNode match, SyntaxNode currentConditional) + TContainer container, SyntaxNode match) where TContainer : SyntaxNode { if (whenPartIsNullable) { - if (match.Parent is TMemberAccessExpression memberAccess) + if (syntaxFacts.IsSimpleMemberAccessExpression(match.Parent)) { + var memberAccess = match.Parent; var nameNode = syntaxFacts.GetNameOfMemberAccessExpression(memberAccess); syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out var arity); var comparer = syntaxFacts.StringComparer; @@ -119,23 +193,22 @@ private SyntaxNode CreateConditionalAccessExpression( // // goo?.Bar() not goo?.Value.Bar(); return CreateConditionalAccessExpression( - syntaxFacts, generator, whenPart, match, - memberAccess.Parent!, currentConditional); + syntaxFacts, generator, container, match, memberAccess.GetRequiredParent()); } } } return CreateConditionalAccessExpression( - syntaxFacts, generator, whenPart, match, - match.Parent!, currentConditional); + syntaxFacts, generator, container, match, match.GetRequiredParent()); } - private SyntaxNode CreateConditionalAccessExpression( + private TContainer? CreateConditionalAccessExpression( ISyntaxFactsService syntaxFacts, SyntaxGeneratorInternal generator, - SyntaxNode whenPart, SyntaxNode match, SyntaxNode matchParent, SyntaxNode currentConditional) + TContainer whenPart, SyntaxNode match, SyntaxNode matchParent) where TContainer : SyntaxNode { - if (matchParent is TMemberAccessExpression memberAccess) + if (syntaxFacts.IsSimpleMemberAccessExpression(matchParent)) { + var memberAccess = matchParent; return whenPart.ReplaceNode(memberAccess, generator.ConditionalAccessExpression( match, @@ -143,16 +216,16 @@ private SyntaxNode CreateConditionalAccessExpression( syntaxFacts.GetNameOfMemberAccessExpression(memberAccess)))); } - if (matchParent is TElementAccessExpression elementAccess) + if (matchParent is TElementAccessExpressionSyntax elementAccess) { Debug.Assert(syntaxFacts.IsElementAccessExpression(elementAccess)); - var argumentList = (TElementBindingArgumentList)syntaxFacts.GetArgumentListOfElementAccessExpression(elementAccess)!; + var argumentList = (TElementBindingArgumentListSyntax)syntaxFacts.GetArgumentListOfElementAccessExpression(elementAccess)!; return whenPart.ReplaceNode(elementAccess, generator.ConditionalAccessExpression( match, ElementBindingExpression(argumentList))); } - return currentConditional; + return null; } } } diff --git a/src/Analyzers/VisualBasic/Analyzers/UseNullPropagation/VisualBasicUseNullPropagationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/UseNullPropagation/VisualBasicUseNullPropagationDiagnosticAnalyzer.vb index 925ce785b3e2b..c6f8bfd731f27 100644 --- a/src/Analyzers/VisualBasic/Analyzers/UseNullPropagation/VisualBasicUseNullPropagationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/UseNullPropagation/VisualBasicUseNullPropagationDiagnosticAnalyzer.vb @@ -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. +Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.LanguageServices @@ -15,12 +16,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation Inherits AbstractUseNullPropagationDiagnosticAnalyzer(Of SyntaxKind, ExpressionSyntax, + ExecutableStatementSyntax, TernaryConditionalExpressionSyntax, BinaryExpressionSyntax, InvocationExpressionSyntax, - MemberAccessExpressionSyntax, ConditionalAccessExpressionSyntax, - InvocationExpressionSyntax) + InvocationExpressionSyntax, + MultiLineIfBlockSyntax, + ExpressionStatementSyntax) + + Protected Overrides ReadOnly Property IfStatementSyntaxKind As SyntaxKind = SyntaxKind.MultiLineIfBlock Protected Overrides Function ShouldAnalyze(compilation As Compilation) As Boolean Return DirectCast(compilation, VisualBasicCompilation).LanguageVersion >= LanguageVersion.VisualBasic14 @@ -34,10 +39,34 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation Return node.IsInExpressionTree(semanticModel, expressionTypeOpt, cancellationToken) End Function - Protected Overrides Function TryAnalyzePatternCondition(syntaxFacts As ISyntaxFacts, conditionNode As SyntaxNode, ByRef conditionPartToCheck As SyntaxNode, ByRef isEquals As Boolean) As Boolean + Protected Overrides Function TryAnalyzePatternCondition(syntaxFacts As ISyntaxFacts, conditionNode As ExpressionSyntax, ByRef conditionPartToCheck As ExpressionSyntax, ByRef isEquals As Boolean) As Boolean + ' VB does not support patterns. conditionPartToCheck = Nothing isEquals = False Return False End Function + + Protected Overrides Function TryGetPartsOfIfStatement( + ifStatement As MultiLineIfBlockSyntax, + ByRef condition As ExpressionSyntax, + ByRef trueStatement As ExecutableStatementSyntax) As Boolean + + condition = ifStatement.IfStatement.Condition + + If ifStatement.ElseBlock IsNot Nothing Then + Return False + End If + + If ifStatement.ElseIfBlocks.Count > 0 Then + Return False + End If + + If ifStatement.Statements.Count <> 1 Then + Return False + End If + + trueStatement = TryCast(ifStatement.Statements(0), ExecutableStatementSyntax) + Return trueStatement IsNot Nothing + End Function End Class End Namespace diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb index 39301b1075045..abc673c6e68c8 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb @@ -14,13 +14,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation Inherits AbstractUseNullPropagationCodeFixProvider(Of SyntaxKind, ExpressionSyntax, + ExecutableStatementSyntax, TernaryConditionalExpressionSyntax, BinaryExpressionSyntax, InvocationExpressionSyntax, - MemberAccessExpressionSyntax, ConditionalAccessExpressionSyntax, InvocationExpressionSyntax, InvocationExpressionSyntax, + MultiLineIfBlockSyntax, + ExpressionStatementSyntax, ArgumentListSyntax) diff --git a/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb b/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb index 34a44fa4d149f..670c449636d0b 100644 --- a/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb @@ -50,6 +50,21 @@ Class C End Class", New TestParameters(VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.VisualBasic12))) End Function + + Public Async Function TestMissingInVB12_IfStatement() As Task + Await TestMissingAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o.ToString() + End If + End Sub +End Class", New TestParameters(VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.VisualBasic12))) + End Function + Public Async Function TestRight_Equals() As Task Await TestInRegularAndScriptAsync( @@ -92,6 +107,77 @@ Class C End Class") End Function + + Public Async Function TestLeft_NotEquals_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [|If|] (o IsNot Nothing) + o.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.ToString() + End Sub +End Class") + End Function + + + Public Async Function TestIfStatement_NotWithElse() As Task + Await TestMissingInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o.ToString() + Else + End If + End Sub +End Class") + End Function + + + Public Async Function TestIfStatement_NotWithElseIf() As Task + Await TestMissingInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o.ToString() + ElseIf (o IsNot Nothing) + End If + End Sub +End Class") + End Function + + + Public Async Function TestIfStatement_NotWithMultipleStatements() As Task + Await TestMissingInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o.ToString() + o.ToString() + End If + End Sub +End Class") + End Function + Public Async Function TestWithNullableType() As Task Await TestInRegularAndScriptAsync( @@ -115,6 +201,31 @@ Class C End Class") End Function + + Public Async Function TestWithNullableType_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Dim f As Integer? + Sub M(C c) + [||]If (c IsNot Nothing) + c.f?.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Dim f As Integer? + Sub M(C c) + c?.f?.ToString() + End Sub +End Class") + End Function + Public Async Function TestWithNullableTypeAndObjectCast() As Task Await TestInRegularAndScriptAsync( @@ -138,6 +249,31 @@ Class C End Class") End Function + + Public Async Function TestWithNullableTypeAndObjectCast_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Dim f As Integer? + Sub M(C c) + [||]If (DirectCast(c, Object) IsNot Nothing) + c.f?.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Dim f As Integer? + Sub M(C c) + c?.f?.ToString() + End Sub +End Class") + End Function + Public Async Function TestRight_NotEquals() As Task Await TestInRegularAndScriptAsync( @@ -159,6 +295,29 @@ Class C End Class") End Function + + Public Async Function TestRight_NotEquals_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (Nothing IsNot o) + o.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.ToString() + End Sub +End Class") + End Function + Public Async Function TestIndexer() As Task Await TestInRegularAndScriptAsync( @@ -180,6 +339,29 @@ Class C End Class") End Function + + Public Async Function TestIndexer_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o(0) + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?(0) + End Sub +End Class") + End Function + Public Async Function TestConditionalAccess() As Task Await TestInRegularAndScriptAsync( @@ -201,6 +383,29 @@ Class C End Class") End Function + + Public Async Function TestConditionalAccess_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o.B?.C + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.B?.C + End Sub +End Class") + End Function + Public Async Function TestMemberAccess() As Task Await TestInRegularAndScriptAsync( @@ -222,6 +427,29 @@ Class C End Class") End Function + + Public Async Function TestMemberAccess_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (o IsNot Nothing) + o.B + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.B + End Sub +End Class") + End Function + Public Async Function TestMissingOnSimpleMatch() As Task Await TestMissingInRegularAndScriptAsync( @@ -256,6 +484,29 @@ Class C End Class") End Function + + Public Async Function TestParenthesizedCondition_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If ((o IsNot Nothing)) + o.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.ToString() + End Sub +End Class") + End Function + Public Async Function TestFixAll1() As Task Await TestInRegularAndScriptAsync( @@ -348,6 +599,30 @@ Class C End Class") End Function + + + Public Async Function TestWithNullableTypeAndReferenceEquals1_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (not ReferenceEquals(o, Nothing)) + o.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.ToString() + End Sub +End Class") + End Function + Public Async Function TestWithNullableTypeAndReferenceEquals2() As Task @@ -664,6 +939,30 @@ Class C End Class") End Function + + + Public Async Function TestEqualsWithLogicalNot_IfStatement() As Task + Await TestInRegularAndScriptAsync( +" +Imports System + +Class C + Sub M(o As Object) + [||]If (Not (o Is Nothing)) + o.ToString() + End If + End Sub +End Class", +" +Imports System + +Class C + Sub M(o As Object) + o?.ToString() + End Sub +End Class") + End Function + Public Async Function TestNotEqualsWithLogicalNot() As Task diff --git a/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt b/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt index 691883be01a72..4c8f5c4788aaa 100644 --- a/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt +++ b/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt @@ -42,6 +42,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsPartial.get -> bo Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsReadOnly.get -> bool Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsRef.get -> bool Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsRequired.get -> bool +Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsFile.get -> bool Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsSealed.get -> bool Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsStatic.get -> bool Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsUnsafe.get -> bool @@ -58,6 +59,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsOverride(bool Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsReadOnly(bool isReadOnly) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsRef(bool isRef) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsRequired(bool isRequired) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers +Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsFile(bool isFile) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsSealed(bool isSealed) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsStatic(bool isStatic) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsUnsafe(bool isUnsafe) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers @@ -87,6 +89,7 @@ static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Partial.get static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.ReadOnly.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Ref.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Required.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers +static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.File.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Sealed.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Static.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.TryParse(string value, out Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers modifiers) -> bool diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs index 7c45b4cd1e836..b9ae6f4c7b29b 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs @@ -75,7 +75,7 @@ internal BinderFactory(CSharpCompilation compilation, SyntaxTree syntaxTree, boo // more than 50 items added before getting collected. _binderCache = new ConcurrentCache(50); - _buckStopsHereBinder = new BuckStopsHereBinder(compilation); + _buckStopsHereBinder = new BuckStopsHereBinder(compilation, syntaxTree); } internal SyntaxTree SyntaxTree diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs index 7f81b7826e5bb..a827850296103 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs @@ -410,6 +410,12 @@ private static void CheckConstraintTypeVisibility( // "Inconsistent accessibility: constraint type '{1}' is less accessible than '{0}'" diagnostics.Add(ErrorCode.ERR_BadVisBound, location, containingSymbol, constraintType.Type); } + + if (constraintType.Type.IsFileTypeOrUsesFileTypes() && !containingSymbol.ContainingType.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, location, constraintType.Type, containingSymbol); + } + diagnostics.Add(location, useSiteInfo); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index 7dffbb5ccce28..47bd4d832fccd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -1306,6 +1306,38 @@ internal static ImmutableArray GetCandidateMembers(NamespaceOrTypeSymbol } } + private bool IsInScopeOfAssociatedSyntaxTree(Symbol symbol) + { + while (symbol is not null and not SourceMemberContainerTypeSymbol { IsFile: true }) + { + symbol = symbol.ContainingType; + } + + if (symbol is null) + { + // the passed-in symbol was not contained in a file type. + return true; + } + + var tree = getSyntaxTreeForFileTypes(); + return symbol.IsDefinedInSourceTree(tree, definedWithinSpan: null); + + SyntaxTree getSyntaxTreeForFileTypes() + { + for (var binder = this; binder != null; binder = binder.Next) + { + if (binder is BuckStopsHereBinder lastBinder) + { + Debug.Assert(lastBinder.AssociatedSyntaxTree is not null); + return lastBinder.AssociatedSyntaxTree; + } + } + + Debug.Assert(false); + return null; + } + } + /// /// Distinguish from , which performs an analogous task for Add*LookupSymbolsInfo*. /// @@ -1322,8 +1354,12 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio ? ((AliasSymbol)symbol).GetAliasTarget(basesBeingResolved) : symbol; + if (!IsInScopeOfAssociatedSyntaxTree(unwrappedSymbol)) + { + return LookupResult.Empty(); + } // Check for symbols marked with 'Microsoft.CodeAnalysis.Embedded' attribute - if (!this.Compilation.SourceModule.Equals(unwrappedSymbol.ContainingModule) && unwrappedSymbol.IsHiddenByCodeAnalysisEmbeddedAttribute()) + else if (!this.Compilation.SourceModule.Equals(unwrappedSymbol.ContainingModule) && unwrappedSymbol.IsHiddenByCodeAnalysisEmbeddedAttribute()) { return LookupResult.Empty(); } @@ -1522,6 +1558,10 @@ internal bool CanAddLookupSymbolInfo(Symbol symbol, LookupOptions options, Looku { return false; } + else if (!IsInScopeOfAssociatedSyntaxTree(symbol)) + { + return false; + } else if ((options & LookupOptions.MustBeInstance) != 0 && !IsInstance(symbol)) { return false; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index caded00d3be20..ee5c62da9ec38 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -1816,6 +1816,16 @@ Symbol resultSymbol( Debug.Assert(!Symbol.Equals(first, second, TypeCompareKind.ConsiderEverything) || !Symbol.Equals(originalSymbols[best.Index], originalSymbols[secondBest.Index], TypeCompareKind.ConsiderEverything), "Why does the LookupResult contain the same symbol twice?"); + if (best.IsFromFile && !secondBest.IsFromFile) + { + // a lookup of a file type is "better" than a lookup of a non-file type; no need to further diagnose + // https://github.com/dotnet/roslyn/issues/62331 + // some "single symbol" diagnostics are missed here for similar reasons + // that make us miss diagnostics when reporting WRN_SameFullNameThisAggAgg. + // + return first; + } + CSDiagnosticInfo info; bool reportError; @@ -2133,6 +2143,7 @@ private static AssemblySymbol GetContainingAssembly(Symbol symbol) private enum BestSymbolLocation { None, + FromFile, FromSourceModule, FromAddedModule, FromReferencedAssembly, @@ -2180,6 +2191,14 @@ public bool IsFromCompilation } } + public bool IsFromFile + { + get + { + return _location == BestSymbolLocation.FromFile; + } + } + public bool IsNone { get @@ -2281,6 +2300,11 @@ private BestSymbolInfo GetBestSymbolInfo(ArrayBuilder symbols, out BestS private static BestSymbolLocation GetLocation(CSharpCompilation compilation, Symbol symbol) { + if (symbol is SourceMemberContainerTypeSymbol { IsFile: true }) + { + return BestSymbolLocation.FromFile; + } + var containingAssembly = symbol.ContainingAssembly; if (containingAssembly == compilation.SourceAssembly) { @@ -2470,7 +2494,8 @@ protected AssemblySymbol GetForwardedToAssembly(string name, int arity, ref Name // NOTE: This won't work if the type isn't using CLS-style generic naming (i.e. `arity), but this code is // only intended to improve diagnostic messages, so false negatives in corner cases aren't a big deal. - var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity); + // File types can't be forwarded, so we won't attempt to determine a file identifier to attach to the metadata name. + var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity, associatedFileIdentifier: null); var fullMetadataName = MetadataHelpers.BuildQualifiedName(qualifierOpt?.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat), metadataName); var result = GetForwardedToAssembly(fullMetadataName, diagnostics, location); if ((object)result != null) diff --git a/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs b/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs index 9c3a274d28542..caa0df8490196 100644 --- a/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs @@ -15,11 +15,21 @@ namespace Microsoft.CodeAnalysis.CSharp /// internal class BuckStopsHereBinder : Binder { - internal BuckStopsHereBinder(CSharpCompilation compilation) + internal BuckStopsHereBinder(CSharpCompilation compilation, SyntaxTree? associatedSyntaxTree) : base(compilation) { + this.AssociatedSyntaxTree = associatedSyntaxTree; } + /// + /// In non-speculative scenarios, the syntax tree being bound. + /// In speculative scenarios, the syntax tree from the original compilation used as the speculation context. + /// This is in some scenarios, such as the binder used for + /// or the binder used to bind usings in . + /// https://github.com/dotnet/roslyn/issues/62332: what about in EE scenarios? + /// + internal readonly SyntaxTree? AssociatedSyntaxTree; + internal override ImportChain? ImportChain { get diff --git a/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs b/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs index 8e0ec9d5aa511..bb782869d4769 100644 --- a/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs +++ b/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs @@ -18,7 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp /// whether to emit an executable or a library, whether to optimize /// generated code, and so on. /// +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode(). public sealed class CSharpCompilationOptions : CompilationOptions, IEquatable +#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode(). { /// /// Allow unsafe regions (i.e. unsafe modifiers on members and unsafe blocks). @@ -748,9 +750,9 @@ public override bool Equals(object? obj) return this.Equals(obj as CSharpCompilationOptions); } - public override int GetHashCode() + protected override int ComputeHashCode() { - return Hash.Combine(base.GetHashCodeHelper(), + return Hash.Combine(GetHashCodeHelper(), Hash.Combine(this.AllowUnsafe, Hash.Combine(Hash.CombineValues(this.Usings, StringComparer.Ordinal), Hash.Combine(TopLevelBinderFlags.GetHashCode(), this.NullableContextOptions.GetHashCode())))); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index d020c8770a693..e07d03a21ee8c 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7124,6 +7124,24 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ '{2}' cannot satisfy the 'new()' constraint on parameter '{1}' in the generic type or or method '{0}' because '{2}' has required members. + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + File type '{0}' cannot use accessibility modifiers. + + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + File type '{0}' cannot be used in a 'global using static' directive. + + + Types and aliases cannot be named 'file'. + unsigned right shift @@ -7157,4 +7175,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF-8 byte representations + + file types + diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs index 2848b30694105..09f02a1f032e3 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs @@ -34,7 +34,7 @@ public static UsingsFromOptionsAndDiagnostics FromOptions(CSharpCompilation comp } var diagnostics = new DiagnosticBag(); - var usingsBinder = new InContainerBinder(compilation.GlobalNamespace, new BuckStopsHereBinder(compilation)); + var usingsBinder = new InContainerBinder(compilation.GlobalNamespace, new BuckStopsHereBinder(compilation, associatedSyntaxTree: null)); var boundUsings = ArrayBuilder.GetInstance(); var uniqueUsings = PooledHashSet.GetInstance(); diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 022eb88de3f7a..b3e13fc59505b 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -82,7 +82,7 @@ internal Conversions Conversions { if (_conversions == null) { - Interlocked.CompareExchange(ref _conversions, new BuckStopsHereBinder(this).Conversions, null); + Interlocked.CompareExchange(ref _conversions, new BuckStopsHereBinder(this, associatedSyntaxTree: null).Conversions, null); } return _conversions; diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index 1d56e63bbf85e..8776d29626174 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -1289,14 +1289,14 @@ Symbol getAttributeTarget(SyntaxNode targetSyntax) { return targetSyntax switch { - BaseMethodDeclarationSyntax methodDeclaration => GetDeclaredMemberSymbol(methodDeclaration), - LocalFunctionStatementSyntax localFunction => GetMemberModel(localFunction)?.GetDeclaredLocalFunction(localFunction), - ParameterSyntax parameterSyntax => ((Symbols.PublicModel.ParameterSymbol)GetDeclaredSymbol(parameterSyntax)).UnderlyingSymbol, - TypeParameterSyntax typeParameterSyntax => ((Symbols.PublicModel.TypeParameterSymbol)GetDeclaredSymbol(typeParameterSyntax)).UnderlyingSymbol, - IndexerDeclarationSyntax indexerSyntax => ((Symbols.PublicModel.PropertySymbol)GetDeclaredSymbol(indexerSyntax)).UnderlyingSymbol, - AccessorDeclarationSyntax accessorSyntax => ((Symbols.PublicModel.MethodSymbol)GetDeclaredSymbol(accessorSyntax)).UnderlyingSymbol, - AnonymousFunctionExpressionSyntax anonymousFunction => ((Symbols.PublicModel.Symbol)GetSymbolInfo(anonymousFunction).Symbol).UnderlyingSymbol, - DelegateDeclarationSyntax delegateSyntax => ((Symbols.PublicModel.NamedTypeSymbol)GetDeclaredSymbol(delegateSyntax)).UnderlyingSymbol, + BaseMethodDeclarationSyntax or + LocalFunctionStatementSyntax or + ParameterSyntax or + TypeParameterSyntax or + IndexerDeclarationSyntax or + AccessorDeclarationSyntax or + DelegateDeclarationSyntax => GetDeclaredSymbolForNode(targetSyntax).GetSymbol(), + AnonymousFunctionExpressionSyntax anonymousFunction => GetSymbolInfo(anonymousFunction).Symbol.GetSymbol(), _ => null }; } diff --git a/src/Compilers/CSharp/Portable/Compiler/DocumentationCommentCompiler.IncludeElementExpander.cs b/src/Compilers/CSharp/Portable/Compiler/DocumentationCommentCompiler.IncludeElementExpander.cs index ad5544fa88802..ca66c4ca60479 100644 --- a/src/Compilers/CSharp/Portable/Compiler/DocumentationCommentCompiler.IncludeElementExpander.cs +++ b/src/Compilers/CSharp/Portable/Compiler/DocumentationCommentCompiler.IncludeElementExpander.cs @@ -536,7 +536,7 @@ private void BindName(XAttribute attribute, CSharpSyntaxNode originatingSyntax, "Why are we processing a documentation comment that is not attached to a member declaration?"); var nameDiagnostics = BindingDiagnosticBag.GetInstance(_diagnostics); - Binder binder = MakeNameBinder(isParameter, isTypeParameterRef, _memberSymbol, _compilation); + Binder binder = MakeNameBinder(isParameter, isTypeParameterRef, _memberSymbol, _compilation, originatingSyntax.SyntaxTree); DocumentationCommentCompiler.BindName(attrSyntax, binder, _memberSymbol, ref _documentedParameters, ref _documentedTypeParameters, nameDiagnostics); RecordBindingDiagnostics(nameDiagnostics, sourceLocation); // Respects DocumentationMode. nameDiagnostics.Free(); @@ -545,9 +545,9 @@ private void BindName(XAttribute attribute, CSharpSyntaxNode originatingSyntax, // NOTE: We're not sharing code with the BinderFactory visitor, because we already have the // member symbol in hand, which makes things much easier. - private static Binder MakeNameBinder(bool isParameter, bool isTypeParameterRef, Symbol memberSymbol, CSharpCompilation compilation) + private static Binder MakeNameBinder(bool isParameter, bool isTypeParameterRef, Symbol memberSymbol, CSharpCompilation compilation, SyntaxTree syntaxTree) { - Binder binder = new BuckStopsHereBinder(compilation); + Binder binder = new BuckStopsHereBinder(compilation, syntaxTree); // All binders should have a containing symbol. Symbol containingSymbol = memberSymbol.ContainingSymbol; diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs index a63ef846b95fb..dc617f84502b4 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs @@ -37,9 +37,10 @@ internal enum DeclarationModifiers : uint Ref = 1 << 21, // used only for structs Required = 1 << 22, // Used only for properties and fields Scoped = 1 << 23, + File = 1 << 24, // used only for types - All = (1 << 24) - 1, // all modifiers - Unset = 1 << 24, // used when a modifiers value hasn't yet been computed + All = (1 << 25) - 1, // all modifiers + Unset = 1 << 25, // used when a modifiers value hasn't yet been computed AccessibilityMask = PrivateProtected | Private | Protected | Internal | ProtectedInternal | Public, } diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.Cache.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.Cache.cs index d308c9fa085e2..afc69df57a4eb 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.Cache.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.Cache.cs @@ -2,12 +2,11 @@ // 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; using System.Linq; +using System.Threading; namespace Microsoft.CodeAnalysis.CSharp { @@ -22,27 +21,72 @@ internal partial class DeclarationTable // (like the type names in those decls). private class Cache { + private readonly DeclarationTable _table; + // The merged root declaration for all the 'old' declarations. - internal readonly Lazy MergedRoot; + private MergedNamespaceDeclaration? _mergedRoot; // All the simple type names for all the types in the 'old' declarations. - internal readonly Lazy> TypeNames; - internal readonly Lazy> NamespaceNames; - internal readonly Lazy> ReferenceDirectives; + private ISet? _typeNames; + private ISet? _namespaceNames; + private ImmutableArray _referenceDirectives; public Cache(DeclarationTable table) { - this.MergedRoot = new Lazy( - () => MergedNamespaceDeclaration.Create(table._allOlderRootDeclarations.InInsertionOrder.AsImmutable())); + _table = table; + } + + public MergedNamespaceDeclaration MergedRoot + { + get + { + if (_mergedRoot is null) + { + Interlocked.CompareExchange( + ref _mergedRoot, + MergedNamespaceDeclaration.Create(_table._allOlderRootDeclarations.InInsertionOrder.AsImmutable()), + comparand: null); + } + + return _mergedRoot; + } + } - this.TypeNames = new Lazy>( - () => GetTypeNames(this.MergedRoot.Value)); + public ISet TypeNames + { + get + { + if (_typeNames is null) + Interlocked.CompareExchange(ref _typeNames, GetTypeNames(this.MergedRoot), comparand: null); - this.NamespaceNames = new Lazy>( - () => GetNamespaceNames(this.MergedRoot.Value)); + return _typeNames; + } + } + + public ISet NamespaceNames + { + get + { + if (_namespaceNames is null) + Interlocked.CompareExchange(ref _namespaceNames, GetNamespaceNames(this.MergedRoot), comparand: null); + + return _namespaceNames; + } + } + + public ImmutableArray ReferenceDirectives + { + get + { + if (_referenceDirectives.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize( + ref _referenceDirectives, + MergedRoot.Declarations.OfType().SelectMany(r => r.ReferenceDirectives).AsImmutable()); + } - this.ReferenceDirectives = new Lazy>( - () => MergedRoot.Value.Declarations.OfType().SelectMany(r => r.ReferenceDirectives).AsImmutable()); + return _referenceDirectives; + } } } } diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs index 545a231ba2223..5d5449704b0bf 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.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.Diagnostics; @@ -31,29 +29,26 @@ internal sealed partial class DeclarationTable // All our root declarations. We split these so we can separate out the unchanging 'older' // declarations from the constantly changing 'latest' declaration. private readonly ImmutableSetWithInsertionOrder _allOlderRootDeclarations; - private readonly Lazy _latestLazyRootDeclaration; + private readonly Lazy? _latestLazyRootDeclaration; // The cache of computed values for the old declarations. private readonly Cache _cache; // The lazily computed total merged declaration. - private MergedNamespaceDeclaration _mergedRoot; + private MergedNamespaceDeclaration? _mergedRoot; - private readonly Lazy> _typeNames; - private readonly Lazy> _namespaceNames; - private readonly Lazy> _referenceDirectives; + private ICollection? _typeNames; + private ICollection? _namespaceNames; + private ICollection? _referenceDirectives; private DeclarationTable( ImmutableSetWithInsertionOrder allOlderRootDeclarations, - Lazy latestLazyRootDeclaration, - Cache cache) + Lazy? latestLazyRootDeclaration, + Cache? cache) { _allOlderRootDeclarations = allOlderRootDeclarations; _latestLazyRootDeclaration = latestLazyRootDeclaration; _cache = cache ?? new Cache(this); - _typeNames = new Lazy>(GetMergedTypeNames); - _namespaceNames = new Lazy>(GetMergedNamespaceNames); - _referenceDirectives = new Lazy>(GetMergedReferenceDirectives); } public DeclarationTable AddRootDeclaration(Lazy lazyRootDeclaration) @@ -119,7 +114,7 @@ public MergedNamespaceDeclaration GetMergedRoot(CSharpCompilation compilation) // Internal for unit tests only. internal MergedNamespaceDeclaration CalculateMergedRoot(CSharpCompilation compilation) { - var oldRoot = _cache.MergedRoot.Value; + var oldRoot = _cache.MergedRoot; if (_latestLazyRootDeclaration == null) { return oldRoot; @@ -155,15 +150,15 @@ internal RootNamespaceLocationComparer(CSharpCompilation compilation) [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Avoid " + nameof(SingleNamespaceOrTypeDeclaration.Location) + " since it has a costly allocation on this fast path.")] - public int Compare(SingleNamespaceDeclaration x, SingleNamespaceDeclaration y) + public int Compare(SingleNamespaceDeclaration? x, SingleNamespaceDeclaration? y) { - return _compilation.CompareSourceLocations(x.SyntaxReference, y.SyntaxReference); + return _compilation.CompareSourceLocations(x!.SyntaxReference, y!.SyntaxReference); } } private ICollection GetMergedTypeNames() { - var cachedTypeNames = _cache.TypeNames.Value; + var cachedTypeNames = _cache.TypeNames; if (_latestLazyRootDeclaration == null) { @@ -177,7 +172,7 @@ private ICollection GetMergedTypeNames() private ICollection GetMergedNamespaceNames() { - var cachedNamespaceNames = _cache.NamespaceNames.Value; + var cachedNamespaceNames = _cache.NamespaceNames; if (_latestLazyRootDeclaration == null) { @@ -191,7 +186,7 @@ private ICollection GetMergedNamespaceNames() private ICollection GetMergedReferenceDirectives() { - var cachedReferenceDirectives = _cache.ReferenceDirectives.Value; + var cachedReferenceDirectives = _cache.ReferenceDirectives; if (_latestLazyRootDeclaration == null) { @@ -248,7 +243,10 @@ public ICollection TypeNames { get { - return _typeNames.Value; + if (_typeNames is null) + Interlocked.CompareExchange(ref _typeNames, GetMergedTypeNames(), comparand: null); + + return _typeNames; } } @@ -256,7 +254,10 @@ public ICollection NamespaceNames { get { - return _namespaceNames.Value; + if (_namespaceNames is null) + Interlocked.CompareExchange(ref _namespaceNames, GetMergedNamespaceNames(), comparand: null); + + return _namespaceNames; } } @@ -264,7 +265,10 @@ public IEnumerable ReferenceDirectives { get { - return _referenceDirectives.Value; + if (_referenceDirectives is null) + Interlocked.CompareExchange(ref _referenceDirectives, GetMergedReferenceDirectives(), comparand: null); + + return _referenceDirectives; } } diff --git a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs index bacdf2682c2d7..36ed4323bf2a9 100644 --- a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs @@ -241,6 +241,14 @@ public bool Equals(TypeDeclarationIdentity other) return false; } + if ((object)thisDecl.Location.SourceTree != otherDecl.Location.SourceTree + && ((thisDecl.Modifiers & DeclarationModifiers.File) != 0 + || (otherDecl.Modifiers & DeclarationModifiers.File) != 0)) + { + // declarations of 'file' types are only the same type if they are in the same file + return false; + } + if (thisDecl._kind == DeclarationKind.Enum || thisDecl._kind == DeclarationKind.Delegate) { // oh, so close, but enums and delegates cannot be partial diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs index a4eeac4890e69..4d5b6f4b2c18b 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs @@ -40,6 +40,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return UnderlyingNamedType.AssociatedFileIdentifier(); + } + } +#nullable disable + string Cci.INamedEntity.Name { get diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs index 377645bb42318..ea7196e6596e2 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs @@ -756,6 +756,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return AdaptedNamedTypeSymbol.AssociatedFileIdentifier(); + } + } +#nullable disable + string Cci.INamedEntity.Name { get diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index ad7b7dfddbc90..13803de6d1fff 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -660,7 +660,7 @@ private void ReportExportedTypeNameCollisions(ImmutableArray e // exported types are not emitted in EnC deltas (hence generation 0): string fullEmittedName = MetadataHelpers.BuildQualifiedName( ((Cci.INamespaceTypeReference)type.GetCciAdapter()).NamespaceName, - Cci.MetadataWriter.GetMangledName(type.GetCciAdapter(), generation: 0)); + Cci.MetadataWriter.GetMetadataName(type.GetCciAdapter(), generation: 0)); // First check against types declared in the primary module if (ContainsTopLevelType(fullEmittedName)) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index c5e76deb24497..f5c0e9941c3d1 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2099,6 +2099,13 @@ internal enum ErrorCode ERR_FixedFieldMustNotBeRef = 9049, ERR_RefFieldCannotReferToRefStruct = 9050, + ERR_FileTypeDisallowedInSignature = 9051, + ERR_FileTypeNoExplicitAccessibility = 9052, + ERR_FileTypeBase = 9053, + ERR_FileTypeNested = 9054, + ERR_GlobalUsingStaticFileType = 9055, + ERR_FileTypeNameDisallowed = 9056, + #endregion // Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd) diff --git a/src/Compilers/CSharp/Portable/Errors/LazyObsoleteDiagnosticInfo.cs b/src/Compilers/CSharp/Portable/Errors/LazyObsoleteDiagnosticInfo.cs index 9395a91120f1c..a018d7ed18c74 100644 --- a/src/Compilers/CSharp/Portable/Errors/LazyObsoleteDiagnosticInfo.cs +++ b/src/Compilers/CSharp/Portable/Errors/LazyObsoleteDiagnosticInfo.cs @@ -5,52 +5,40 @@ #nullable disable using System.Diagnostics; -using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; namespace Microsoft.CodeAnalysis.CSharp { - internal sealed class LazyObsoleteDiagnosticInfo : DiagnosticInfo + internal sealed class LazyObsoleteDiagnosticInfo : LazyDiagnosticInfo { - private DiagnosticInfo _lazyActualObsoleteDiagnostic; - private readonly object _symbolOrSymbolWithAnnotations; private readonly Symbol _containingSymbol; private readonly BinderFlags _binderFlags; internal LazyObsoleteDiagnosticInfo(object symbol, Symbol containingSymbol, BinderFlags binderFlags) - : base(CSharp.MessageProvider.Instance, (int)ErrorCode.Unknown) { Debug.Assert(symbol is Symbol || symbol is TypeWithAnnotations); _symbolOrSymbolWithAnnotations = symbol; _containingSymbol = containingSymbol; _binderFlags = binderFlags; - _lazyActualObsoleteDiagnostic = null; } - internal override DiagnosticInfo GetResolvedInfo() + protected override DiagnosticInfo ResolveInfo() { - if (_lazyActualObsoleteDiagnostic == null) - { - // A symbol's Obsoleteness may not have been calculated yet if the symbol is coming - // from a different compilation's source. In that case, force completion of attributes. - var symbol = (_symbolOrSymbolWithAnnotations as Symbol) ?? ((TypeWithAnnotations)_symbolOrSymbolWithAnnotations).Type; - symbol.ForceCompleteObsoleteAttribute(); - - var kind = ObsoleteAttributeHelpers.GetObsoleteDiagnosticKind(symbol, _containingSymbol, forceComplete: true); - Debug.Assert(kind != ObsoleteDiagnosticKind.Lazy); - Debug.Assert(kind != ObsoleteDiagnosticKind.LazyPotentiallySuppressed); - - var info = (kind == ObsoleteDiagnosticKind.Diagnostic) ? - ObsoleteAttributeHelpers.CreateObsoleteDiagnostic(symbol, _binderFlags) : - null; - - // If this symbol is not obsolete or is in an obsolete context, we don't want to report any diagnostics. - // Therefore make this a Void diagnostic. - Interlocked.CompareExchange(ref _lazyActualObsoleteDiagnostic, info ?? CSDiagnosticInfo.VoidDiagnosticInfo, null); - } - - return _lazyActualObsoleteDiagnostic; + // A symbol's Obsoleteness may not have been calculated yet if the symbol is coming + // from a different compilation's source. In that case, force completion of attributes. + var symbol = (_symbolOrSymbolWithAnnotations as Symbol) ?? ((TypeWithAnnotations)_symbolOrSymbolWithAnnotations).Type; + symbol.ForceCompleteObsoleteAttribute(); + + var kind = ObsoleteAttributeHelpers.GetObsoleteDiagnosticKind(symbol, _containingSymbol, forceComplete: true); + Debug.Assert(kind != ObsoleteDiagnosticKind.Lazy); + Debug.Assert(kind != ObsoleteDiagnosticKind.LazyPotentiallySuppressed); + + // If this symbol is not obsolete or is in an obsolete context, we don't want to report any diagnostics. + // Therefore return null. + return (kind == ObsoleteDiagnosticKind.Diagnostic) ? + ObsoleteAttributeHelpers.CreateObsoleteDiagnostic(symbol, _binderFlags) : + null; } } } diff --git a/src/Compilers/CSharp/Portable/Errors/LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo.cs b/src/Compilers/CSharp/Portable/Errors/LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo.cs index 07a8ec29f0c61..e6028a2a9328f 100644 --- a/src/Compilers/CSharp/Portable/Errors/LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo.cs +++ b/src/Compilers/CSharp/Portable/Errors/LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo.cs @@ -8,40 +8,29 @@ namespace Microsoft.CodeAnalysis.CSharp { - internal sealed class LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo : DiagnosticInfo + internal sealed class LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo : LazyDiagnosticInfo { - private DiagnosticInfo? _lazyActualUnmanagedCallersOnlyDiagnostic; - private readonly MethodSymbol _method; private readonly bool _isDelegateConversion; internal LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo(MethodSymbol method, bool isDelegateConversion) - : base(CSharp.MessageProvider.Instance, (int)ErrorCode.Unknown) { _method = method; - _lazyActualUnmanagedCallersOnlyDiagnostic = null; _isDelegateConversion = isDelegateConversion; } - internal override DiagnosticInfo GetResolvedInfo() + protected override DiagnosticInfo? ResolveInfo() { - if (_lazyActualUnmanagedCallersOnlyDiagnostic is null) - { - UnmanagedCallersOnlyAttributeData? unmanagedCallersOnlyAttributeData = _method.GetUnmanagedCallersOnlyAttributeData(forceComplete: true); - Debug.Assert(!ReferenceEquals(unmanagedCallersOnlyAttributeData, UnmanagedCallersOnlyAttributeData.Uninitialized)); - Debug.Assert(!ReferenceEquals(unmanagedCallersOnlyAttributeData, UnmanagedCallersOnlyAttributeData.AttributePresentDataNotBound)); - - var info = unmanagedCallersOnlyAttributeData is null - ? CSDiagnosticInfo.VoidDiagnosticInfo - : new CSDiagnosticInfo(_isDelegateConversion - ? ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeConvertedToDelegate - : ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, - _method); - - Interlocked.CompareExchange(ref _lazyActualUnmanagedCallersOnlyDiagnostic, info, null); - } - - return _lazyActualUnmanagedCallersOnlyDiagnostic; + UnmanagedCallersOnlyAttributeData? unmanagedCallersOnlyAttributeData = _method.GetUnmanagedCallersOnlyAttributeData(forceComplete: true); + Debug.Assert(!ReferenceEquals(unmanagedCallersOnlyAttributeData, UnmanagedCallersOnlyAttributeData.Uninitialized)); + Debug.Assert(!ReferenceEquals(unmanagedCallersOnlyAttributeData, UnmanagedCallersOnlyAttributeData.AttributePresentDataNotBound)); + + return unmanagedCallersOnlyAttributeData is null + ? null + : new CSDiagnosticInfo(_isDelegateConversion + ? ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeConvertedToDelegate + : ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, + _method); } } } diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index a6a514693b458..0cb52e6c2fea5 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -253,9 +253,11 @@ internal enum MessageID IDS_FeatureUnsignedRightShift = MessageBase + 12823, IDS_FeatureExtendedNameofScope = MessageBase + 12824, + IDS_FeatureRelaxedShiftOperator = MessageBase + 12825, IDS_FeatureRequiredMembers = MessageBase + 12826, IDS_FeatureRefFields = MessageBase + 12827, + IDS_FeatureFileTypes = MessageBase + 12828, } // Message IDs may refer to strings that need to be localized. @@ -380,6 +382,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureExtendedNameofScope: // semantic check case MessageID.IDS_FeatureRelaxedShiftOperator: // semantic check case MessageID.IDS_FeatureRefFields: // semantic check + case MessageID.IDS_FeatureFileTypes: // semantic check return LanguageVersion.Preview; // C# 10.0 features. diff --git a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 index ad82fa00e9719..672775a68ae60 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 +++ b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 @@ -99,6 +99,7 @@ modifier | 'async' | 'const' | 'extern' + | 'file' | 'fixed' | 'internal' | 'new' diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs index 6a5506952a903..dcd85c40cb5b9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs @@ -41,7 +41,7 @@ internal sealed class AsyncIteratorMethodToStateMachineRewriter : AsyncMethodToS private readonly LabelSymbol _exprReturnLabelTrue; /// - /// States for `yield return` are decreasing from . + /// States for `yield return` are decreasing from . /// private readonly ResumableStateMachineStateAllocator _iteratorStateAllocator; @@ -71,7 +71,7 @@ internal AsyncIteratorMethodToStateMachineRewriter(MethodSymbol method, _iteratorStateAllocator = new ResumableStateMachineStateAllocator( slotAllocatorOpt, - firstState: StateMachineStates.FirstResumableAsyncIteratorState, + firstState: StateMachineState.FirstResumableAsyncIteratorState, increasing: false); } @@ -217,7 +217,7 @@ protected override BoundBinaryOperator ShouldEnterFinallyBlock() // We don't care about state = -2 (method already completed) // So we only want to enter the finally when the state is -1 - return F.IntEqual(F.Local(cachedState), F.Literal(StateMachineStates.NotStartedOrRunningState)); + return F.IntEqual(F.Local(cachedState), F.Literal(StateMachineState.NotStartedOrRunningState)); } #region Visitors @@ -236,7 +236,7 @@ protected override BoundStatement VisitBody(BoundStatement body) // this.state = cachedState = -1; // ... rewritten body - AddState(StateMachineStates.InitialAsyncIteratorState, out GeneratedLabelSymbol resumeLabel); + AddState(StateMachineState.InitialAsyncIteratorState, out GeneratedLabelSymbol resumeLabel); var rewrittenBody = (BoundStatement)Visit(body); @@ -244,7 +244,7 @@ protected override BoundStatement VisitBody(BoundStatement body) return F.Block( F.Label(resumeLabel), // initialStateResumeLabel: GenerateJumpToCurrentDisposalLabel(), // if (disposeMode) goto _exprReturnLabel; - GenerateSetBothStates(StateMachineStates.NotStartedOrRunningState), // this.state = cachedState = -1; + GenerateSetBothStates(StateMachineState.NotStartedOrRunningState), // this.state = cachedState = -1; rewrittenBody); } @@ -288,7 +288,7 @@ public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement no blockBuilder.Add( // this.state = cachedState = NotStartedStateMachine - GenerateSetBothStates(StateMachineStates.NotStartedOrRunningState)); + GenerateSetBothStates(StateMachineState.NotStartedOrRunningState)); Debug.Assert(_currentDisposalLabel is object); // no yield return allowed inside a finally blockBuilder.Add( diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs index 65634991addeb..d9767dbac8ba1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs @@ -124,8 +124,8 @@ private FieldSymbol GetAwaiterField(TypeSymbol awaiterType) protected sealed override string EncMissingStateMessage => CodeAnalysisResources.EncCannotResumeSuspendedAsyncMethod; - protected sealed override int FirstIncreasingResumableState - => StateMachineStates.FirstResumableAsyncState; + protected sealed override StateMachineState FirstIncreasingResumableState + => StateMachineState.FirstResumableAsyncState; /// /// Generate the body for MoveNext(). @@ -161,7 +161,7 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) bodyBuilder.Add(F.Label(_exprReturnLabel)); // this.state = finishedState - var stateDone = F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineStates.FinishedState)); + var stateDone = F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState)); var block = body.Syntax as BlockSyntax; if (block == null) { @@ -235,7 +235,7 @@ protected BoundCatchBlock GenerateExceptionHandling(LocalSymbol exceptionLocal, // _state = finishedState; BoundStatement assignFinishedState = - F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineStates.FinishedState))); + F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState))); // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); BoundStatement callSetException = GenerateSetExceptionCall(exceptionLocal); @@ -443,7 +443,7 @@ private BoundExpression GenerateGetIsCompleted(LocalSymbol awaiterTemp, MethodSy private BoundBlock GenerateAwaitForIncompleteTask(LocalSymbol awaiterTemp) { - AddResumableState(awaiterTemp.GetDeclaratorSyntax(), out int stateNumber, out GeneratedLabelSymbol resumeLabel); + AddResumableState(awaiterTemp.GetDeclaratorSyntax(), out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel); TypeSymbol awaiterFieldType = awaiterTemp.Type.IsVerifierReference() ? F.SpecialType(SpecialType.System_Object) @@ -497,7 +497,7 @@ private BoundBlock GenerateAwaitForIncompleteTask(LocalSymbol awaiterTemp) blockBuilder.Add( // this.state = cachedState = NotStartedStateMachine - GenerateSetBothStates(StateMachineStates.NotStartedOrRunningState)); + GenerateSetBothStates(StateMachineState.NotStartedOrRunningState)); return F.Block(blockBuilder.ToImmutableAndFree()); } diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs index f4f79b3c80f02..ecb0a4c6389bf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs @@ -195,7 +195,7 @@ private BoundExpressionStatement GenerateCreateAndAssignBuilder() protected override void InitializeStateMachine(ArrayBuilder bodyBuilder, NamedTypeSymbol frameType, LocalSymbol stateMachineLocal) { // var stateMachineLocal = new {StateMachineType}({initialState}) - int initialState = _isEnumerable ? StateMachineStates.FinishedState : StateMachineStates.InitialAsyncIteratorState; + var initialState = _isEnumerable ? StateMachineState.FinishedState : StateMachineState.InitialAsyncIteratorState; bodyBuilder.Add( F.Assignment( F.Local(stateMachineLocal), @@ -323,7 +323,7 @@ private void GenerateIAsyncEnumeratorImplementation_MoveNextAsync() BoundStatement ifFinished = F.If( // if (state == StateMachineStates.FinishedStateMachine) - F.IntEqual(F.InstanceField(stateField), F.Literal(StateMachineStates.FinishedState)), + F.IntEqual(F.InstanceField(stateField), F.Literal(StateMachineState.FinishedState)), // return default; thenClause: F.Return(F.Default(moveNextAsyncReturnType))); @@ -433,13 +433,13 @@ private void GenerateIAsyncDisposable_DisposeAsync() BoundStatement ifInvalidState = F.If( // if (state >= StateMachineStates.NotStartedStateMachine /* -1 */) - F.IntGreaterThanOrEqual(F.InstanceField(stateField), F.Literal(StateMachineStates.NotStartedOrRunningState)), + F.IntGreaterThanOrEqual(F.InstanceField(stateField), F.Literal(StateMachineState.NotStartedOrRunningState)), // throw new NotSupportedException(); thenClause: F.Throw(F.New(F.WellKnownType(WellKnownType.System_NotSupportedException)))); BoundStatement ifFinished = F.If( // if (state == StateMachineStates.FinishedStateMachine) - F.IntEqual(F.InstanceField(stateField), F.Literal(StateMachineStates.FinishedState)), + F.IntEqual(F.InstanceField(stateField), F.Literal(StateMachineState.FinishedState)), // return default; thenClause: F.Return(F.Default(returnType))); @@ -651,10 +651,10 @@ private void GenerateIAsyncEnumerableImplementation_GetAsyncEnumerator() .AsMember(IAsyncEnumerableOfElementType); BoundExpression managedThreadId = null; - GenerateIteratorGetEnumerator(IAsyncEnumerableOfElementType_GetEnumerator, ref managedThreadId, initialState: StateMachineStates.InitialAsyncIteratorState); + GenerateIteratorGetEnumerator(IAsyncEnumerableOfElementType_GetEnumerator, ref managedThreadId, initialState: StateMachineState.InitialAsyncIteratorState); } - protected override void GenerateResetInstance(ArrayBuilder builder, int initialState) + protected override void GenerateResetInstance(ArrayBuilder builder, StateMachineState initialState) { // this.state = {initialState}; // this.builder = System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Create(); diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs index 6616076c64846..28bfc2bbc0906 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs @@ -230,7 +230,7 @@ protected override BoundStatement GenerateStateMachineCreation(LocalSymbol state bodyBuilder.Add( F.Assignment( F.Field(F.Local(stateMachineVariable), stateField.AsMember(frameType)), - F.Literal(StateMachineStates.NotStartedOrRunningState))); + F.Literal(StateMachineState.NotStartedOrRunningState))); // local.$builder.Start(ref local) -- binding to the method AsyncTaskMethodBuilder.Start() var startMethod = methodScopeAsyncMethodBuilderMemberCollection.Start.Construct(frameType); diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.IteratorFinallyFrame.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.IteratorFinallyFrame.cs index 24656238528aa..8a0f05195c49c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.IteratorFinallyFrame.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.IteratorFinallyFrame.cs @@ -16,7 +16,7 @@ internal partial class IteratorMethodToStateMachineRewriter private sealed class IteratorFinallyFrame { // finalize state of this frame. This is the state we should be in when we are "between real states" - public readonly int finalizeState; + public readonly StateMachineState finalizeState; // Enclosing frame. Root frame does not have parent. public readonly IteratorFinallyFrame parent; @@ -28,7 +28,7 @@ private sealed class IteratorFinallyFrame // This is enough information to restore Try/Finally tree structure in Dispose and dispatch any valid state // into a corresponding try. // NOTE: union of all values in this map gives all nested frames. - public Dictionary knownStates; + public Dictionary knownStates; // labels within this frame (branching to these labels does not go through finally). public readonly HashSet labels; @@ -42,7 +42,7 @@ private sealed class IteratorFinallyFrame public IteratorFinallyFrame( IteratorFinallyFrame parent, - int finalizeState, + StateMachineState finalizeState, IteratorFinallyMethodSymbol handler, HashSet labels) { @@ -57,7 +57,7 @@ public IteratorFinallyFrame( public IteratorFinallyFrame() { - this.finalizeState = StateMachineStates.NotStartedOrRunningState; + this.finalizeState = StateMachineState.NotStartedOrRunningState; } public bool IsRoot() @@ -65,7 +65,7 @@ public bool IsRoot() return this.parent == null; } - public void AddState(int state) + public void AddState(StateMachineState state) { if (parent != null) { @@ -76,12 +76,12 @@ public void AddState(int state) // Notifies all parents about the state recursively. // All parents need to know states they recursively contain and what // immediate child can handle every particular state. - private void AddState(int state, IteratorFinallyFrame innerHandler) + private void AddState(StateMachineState state, IteratorFinallyFrame innerHandler) { var knownStates = this.knownStates; if (knownStates == null) { - this.knownStates = knownStates = new Dictionary(); + this.knownStates = knownStates = new Dictionary(); } knownStates.Add(state, innerHandler); diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs index 2b647596d65c3..98ad50d5ac1e3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs @@ -53,7 +53,7 @@ internal sealed partial class IteratorMethodToStateMachineRewriter : MethodToSta /// The purpose of distinct finally states is to have enough information about /// which finally handlers must run when we need to finalize iterator after a fault. /// - private int _nextFinalizeState; + private StateMachineState _nextFinalizeState; internal IteratorMethodToStateMachineRewriter( SyntheticBoundNodeFactory F, @@ -71,14 +71,14 @@ internal IteratorMethodToStateMachineRewriter( { _current = current; - _nextFinalizeState = slotAllocatorOpt?.GetFirstUnusedStateMachineState(increasing: false) ?? StateMachineStates.FirstIteratorFinalizeState; + _nextFinalizeState = slotAllocatorOpt?.GetFirstUnusedStateMachineState(increasing: false) ?? StateMachineState.FirstIteratorFinalizeState; } protected override string EncMissingStateMessage => CodeAnalysisResources.EncCannotResumeSuspendedIteratorMethod; - protected override int FirstIncreasingResumableState - => StateMachineStates.FirstResumableIteratorState; + protected override StateMachineState FirstIncreasingResumableState + => StateMachineState.FirstResumableIteratorState; internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImplementationMethod moveNextMethod, SynthesizedImplementationMethod disposeMethod) { @@ -95,7 +95,7 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme /////////////////////////////////// F.CurrentFunction = moveNextMethod; - AddState(StateMachineStates.InitialIteratorState, out GeneratedLabelSymbol initialLabel); + AddState(StateMachineState.InitialIteratorState, out GeneratedLabelSymbol initialLabel); var newBody = (BoundStatement)Visit(body); // switch(cachedState) { @@ -118,7 +118,7 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme Dispatch(isOutermost: true), GenerateReturn(finished: true), F.Label(initialLabel), - F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineStates.NotStartedOrRunningState)), + F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineState.NotStartedOrRunningState)), newBody); // @@ -258,7 +258,7 @@ private BoundStatement EmitFinallyFrame(IteratorFinallyFrame frame, BoundLocal s var sections = from ft in frame.knownStates group ft.Key by ft.Value into g select F.SwitchSection( - new List(g), + g.SelectAsArray(state => (int)state), EmitFinallyFrame(g.Key, state), F.Goto(breakLabel)); @@ -328,7 +328,7 @@ public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement no // : ; // // this.state = finalizeState; - AddResumableState(node.Syntax, out int stateNumber, out GeneratedLabelSymbol resumeLabel); + AddResumableState(node.Syntax, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel); _currentFinallyFrame.AddState(stateNumber); var rewrittenExpression = (BoundExpression)Visit(node.Expression); @@ -486,7 +486,7 @@ private bool ContainsYields(BoundTryStatement statement) return _yieldsInTryAnalysis.ContainsYields(statement); } - private IteratorFinallyMethodSymbol MakeSynthesizedFinally(int finalizeState) + private IteratorFinallyMethodSymbol MakeSynthesizedFinally(StateMachineState finalizeState) { var stateMachineType = (IteratorStateMachine)F.CurrentType; var finallyMethod = new IteratorFinallyMethodSymbol(stateMachineType, GeneratedNames.MakeIteratorFinallyMethodName(finalizeState)); diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs index a1429a11033e7..0ba9b23ab9113 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs @@ -250,7 +250,7 @@ private void GenerateEnumerableImplementation(ref BoundExpression managedThreadI var IEnumerableOfElementType_GetEnumerator = F.SpecialMethod(SpecialMember.System_Collections_Generic_IEnumerable_T__GetEnumerator).AsMember(IEnumerableOfElementType); // generate GetEnumerator() - var getEnumeratorGeneric = GenerateIteratorGetEnumerator(IEnumerableOfElementType_GetEnumerator, ref managedThreadId, StateMachineStates.InitialIteratorState); + var getEnumeratorGeneric = GenerateIteratorGetEnumerator(IEnumerableOfElementType_GetEnumerator, ref managedThreadId, StateMachineState.InitialIteratorState); // Generate IEnumerable.GetEnumerator var getEnumerator = OpenMethodImplementation(IEnumerable_GetEnumerator); @@ -287,7 +287,7 @@ protected override void InitializeStateMachine(ArrayBuilder body { // var stateMachineLocal = new IteratorImplementationClass(N) // where N is either 0 (if we're producing an enumerator) or -2 (if we're producing an enumerable) - int initialState = _isEnumerable ? StateMachineStates.FinishedState : StateMachineStates.InitialIteratorState; + var initialState = _isEnumerable ? StateMachineState.FinishedState : StateMachineState.InitialIteratorState; bodyBuilder.Add( F.Assignment( F.Local(stateMachineLocal), diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.DecisionDagRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.DecisionDagRewriter.cs index 47d1513c786a2..37b0373478525 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.DecisionDagRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.DecisionDagRewriter.cs @@ -535,6 +535,13 @@ bool canDispatch(BoundTestDecisionDagNode test1, BoundTestDecisionDagNode test2) return false; if (!t1.Input.Equals(t2.Input)) return false; + + if (t1.Input.Type.SpecialType is SpecialType.System_Double or SpecialType.System_Single) + { + // The optimization (using balanced switch dispatch) breaks the semantics of NaN + return false; + } + return true; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 41ecea365c121..69cbb5f07bf4e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -269,9 +269,15 @@ private BoundBlock RewriteDeclarationUsingStatement( } } + /// + /// The node that declares the type of the resource (might be shared by multiple resource declarations, e.g. using T x = expr, y = expr;) + /// + /// + /// The node that declares the resource storage, e.g. x = expr in using T x = expr, y = expr;. + /// private BoundStatement RewriteUsingStatementTryFinally( - SyntaxNode typeSyntax, - SyntaxNode syntax, + SyntaxNode resourceTypeSyntax, + SyntaxNode resourceSyntax, BoundBlock tryBlock, BoundLocal local, SyntaxToken awaitKeywordOpt, @@ -353,9 +359,9 @@ private BoundStatement RewriteUsingStatementTryFinally( if (isNullableValueType) { - MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(typeSyntax, local.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); + MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(resourceTypeSyntax, local.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // local.GetValueOrDefault() - disposedExpression = BoundCall.Synthesized(syntax, local, getValueOrDefault); + disposedExpression = BoundCall.Synthesized(resourceSyntax, local, getValueOrDefault); } else { @@ -363,17 +369,17 @@ private BoundStatement RewriteUsingStatementTryFinally( disposedExpression = local; } - BoundExpression disposeCall = GenerateDisposeCall(typeSyntax, syntax, disposedExpression, patternDisposeInfo, awaitOpt, awaitKeywordOpt); + BoundExpression disposeCall = GenerateDisposeCall(resourceTypeSyntax, resourceSyntax, disposedExpression, patternDisposeInfo, awaitOpt, awaitKeywordOpt); // local.Dispose(); or await variant - BoundStatement disposeStatement = new BoundExpressionStatement(syntax, disposeCall); + BoundStatement disposeStatement = new BoundExpressionStatement(resourceSyntax, disposeCall); BoundExpression? ifCondition; if (isNullableValueType) { // local.HasValue - ifCondition = _factory.MakeNullableHasValue(syntax, local); + ifCondition = _factory.MakeNullableHasValue(resourceSyntax, local); } else if (local.Type.IsValueType) { @@ -382,7 +388,7 @@ private BoundStatement RewriteUsingStatementTryFinally( else { // local != null - ifCondition = _factory.MakeNullCheck(syntax, local, BinaryOperatorKind.NotEqual); + ifCondition = _factory.MakeNullCheck(resourceSyntax, local, BinaryOperatorKind.NotEqual); } BoundStatement finallyStatement; @@ -400,7 +406,7 @@ private BoundStatement RewriteUsingStatementTryFinally( // or // await variants finallyStatement = RewriteIfStatement( - syntax: syntax, + syntax: resourceSyntax, rewrittenCondition: ifCondition, rewrittenConsequence: disposeStatement, rewrittenAlternativeOpt: null, @@ -411,17 +417,23 @@ private BoundStatement RewriteUsingStatementTryFinally( // or // nullable or await variants BoundStatement tryFinally = new BoundTryStatement( - syntax: syntax, + syntax: resourceSyntax, tryBlock: tryBlock, catchBlocks: ImmutableArray.Empty, - finallyBlockOpt: BoundBlock.SynthesizedNoLocals(syntax, finallyStatement)); + finallyBlockOpt: BoundBlock.SynthesizedNoLocals(resourceSyntax, finallyStatement)); return tryFinally; } + /// + /// The node that declares the type of the resource (might be shared by multiple resource declarations, e.g. using T x = expr, y = expr;) + /// + /// + /// The node that declares the resource storage, e.g. x = expr in using T x = expr, y = expr;. + /// private BoundExpression GenerateDisposeCall( - SyntaxNode typeSyntax, - SyntaxNode syntax, + SyntaxNode resourceTypeSyntax, + SyntaxNode resourceSyntax, BoundExpression disposedExpression, MethodArgumentInfo? disposeInfo, BoundAwaitableInfo? awaitOpt, @@ -436,7 +448,7 @@ private BoundExpression GenerateDisposeCall( if (awaitOpt is null) { // IDisposable.Dispose() - Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, typeSyntax, _diagnostics, out disposeMethod); + Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, resourceTypeSyntax, _diagnostics, out disposeMethod); } else { @@ -448,7 +460,7 @@ private BoundExpression GenerateDisposeCall( BoundExpression disposeCall; if (disposeMethod is null) { - disposeCall = new BoundBadExpression(syntax, LookupResultKind.NotInvocable, ImmutableArray.Empty, ImmutableArray.Create(disposedExpression), ErrorTypeSymbol.UnknownResultType); + disposeCall = new BoundBadExpression(resourceSyntax, LookupResultKind.NotInvocable, ImmutableArray.Empty, ImmutableArray.Create(disposedExpression), ErrorTypeSymbol.UnknownResultType); } else { @@ -458,7 +470,7 @@ private BoundExpression GenerateDisposeCall( disposeInfo = MethodArgumentInfo.CreateParameterlessMethod(disposeMethod); } - disposeCall = MakeCallWithNoExplicitArgument(disposeInfo, syntax, disposedExpression); + disposeCall = MakeCallWithNoExplicitArgument(disposeInfo, resourceSyntax, disposedExpression); if (awaitOpt is object) { @@ -466,7 +478,7 @@ private BoundExpression GenerateDisposeCall( _sawAwaitInExceptionHandler = true; TypeSymbol awaitExpressionType = awaitOpt.GetResult?.ReturnType ?? _compilation.DynamicType; - disposeCall = RewriteAwaitExpression(syntax, disposeCall, awaitOpt, awaitExpressionType, false); + disposeCall = RewriteAwaitExpression(resourceSyntax, disposeCall, awaitOpt, awaitExpressionType, false); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index f9920cb65fbc8..42c94e552ff81 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -61,7 +61,7 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter /// Note that there is a dispatch occurring at every try-finally statement, so this /// variable takes on a new set of values inside each try block. /// - private Dictionary> _dispatches = new Dictionary>(); + private Dictionary> _dispatches = new(); /// /// A pool of fields used to hoist locals. They appear in this set when not in scope, @@ -152,7 +152,7 @@ public MethodToStateMachineRewriter( increasing: true); } - protected abstract int FirstIncreasingResumableState { get; } + protected abstract StateMachineState FirstIncreasingResumableState { get; } protected abstract string EncMissingStateMessage { get; } /// @@ -199,30 +199,30 @@ protected override BoundExpression FramePointer(SyntaxNode syntax, NamedTypeSymb return result; } #nullable enable - protected void AddResumableState(SyntaxNode awaitOrYieldReturnSyntax, out int stateNumber, out GeneratedLabelSymbol resumeLabel) - => AddResumableState(_resumableStateAllocator, awaitOrYieldReturnSyntax, out stateNumber, out resumeLabel); + protected void AddResumableState(SyntaxNode awaitOrYieldReturnSyntax, out StateMachineState state, out GeneratedLabelSymbol resumeLabel) + => AddResumableState(_resumableStateAllocator, awaitOrYieldReturnSyntax, out state, out resumeLabel); - protected void AddResumableState(ResumableStateMachineStateAllocator allocator, SyntaxNode awaitOrYieldReturnSyntax, out int stateNumber, out GeneratedLabelSymbol resumeLabel) + protected void AddResumableState(ResumableStateMachineStateAllocator allocator, SyntaxNode awaitOrYieldReturnSyntax, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel) { stateNumber = allocator.AllocateState(awaitOrYieldReturnSyntax); AddStateDebugInfo(awaitOrYieldReturnSyntax, stateNumber); AddState(stateNumber, out resumeLabel); } - protected void AddStateDebugInfo(SyntaxNode node, int stateNumber) + protected void AddStateDebugInfo(SyntaxNode node, StateMachineState state) { Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(node) || SyntaxBindingUtilities.BindsToTryStatement(node), $"Unexpected syntax: {node.Kind()}"); int syntaxOffset = CurrentMethod.CalculateLocalSyntaxOffset(node.SpanStart, node.SyntaxTree); - _stateDebugInfoBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, stateNumber)); + _stateDebugInfoBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, state)); } - protected void AddState(int stateNumber, out GeneratedLabelSymbol resumeLabel) + protected void AddState(StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel) { - _dispatches ??= new Dictionary>(); + _dispatches ??= new Dictionary>(); resumeLabel = F.GenerateLabel("stateMachine"); - _dispatches.Add(resumeLabel, new List { stateNumber }); + _dispatches.Add(resumeLabel, new List { stateNumber }); } /// @@ -233,7 +233,10 @@ protected void AddState(int stateNumber, out GeneratedLabelSymbol resumeLabel) /// protected BoundStatement Dispatch(bool isOutermost) { - var sections = from kv in _dispatches orderby kv.Value[0] select F.SwitchSection(kv.Value, F.Goto(kv.Key)); + var sections = from kv in _dispatches + orderby kv.Value[0] + select F.SwitchSection(kv.Value.SelectAsArray(state => (int)state), F.Goto(kv.Key)); + var result = F.Switch(F.Local(cachedState), sections.ToImmutableArray()); // Suspension states that were generated for any previous generation of the state machine @@ -834,8 +837,8 @@ public override BoundNode VisitTryStatement(BoundTryStatement node) Dispatch(isOutermost: false), tryBlock); - oldDispatches ??= new Dictionary>(); - oldDispatches.Add(dispatchLabel, new List(from kv in _dispatches.Values from n in kv orderby n select n)); + oldDispatches ??= new Dictionary>(); + oldDispatches.Add(dispatchLabel, new List(from kv in _dispatches.Values from n in kv orderby n select n)); } _dispatches = oldDispatches; @@ -870,13 +873,13 @@ protected virtual BoundBlock VisitFinally(BoundBlock finallyBlock) protected virtual BoundBinaryOperator ShouldEnterFinallyBlock() { - return F.IntLessThan(F.Local(cachedState), F.Literal(StateMachineStates.FirstUnusedState)); + return F.IntLessThan(F.Local(cachedState), F.Literal(StateMachineState.FirstUnusedState)); } /// /// Set the state field and the cached state /// - protected BoundExpressionStatement GenerateSetBothStates(int stateNumber) + protected BoundExpressionStatement GenerateSetBothStates(StateMachineState stateNumber) { // this.state = cachedState = stateNumber; return F.Assignment(F.Field(F.This(), stateField), F.AssignmentExpression(F.Local(cachedState), F.Literal(stateNumber))); diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs index edb186992cd70..ce443a1429a01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs @@ -16,12 +16,12 @@ internal sealed class ResumableStateMachineStateAllocator { private readonly VariableSlotAllocator? _slotAllocator; private readonly bool _increasing; - private readonly int _firstState; + private readonly StateMachineState _firstState; /// /// The number of the next generated resumable state (i.e. state that resumes execution of the state machine after await expression or yield return). /// - private int _nextState; + private StateMachineState _nextState; #if DEBUG /// @@ -34,7 +34,7 @@ internal sealed class ResumableStateMachineStateAllocator /// private int _matchedStateCount; - public ResumableStateMachineStateAllocator(VariableSlotAllocator? slotAllocator, int firstState, bool increasing) + public ResumableStateMachineStateAllocator(VariableSlotAllocator? slotAllocator, StateMachineState firstState, bool increasing) { _increasing = increasing; _slotAllocator = slotAllocator; @@ -43,28 +43,28 @@ public ResumableStateMachineStateAllocator(VariableSlotAllocator? slotAllocator, _nextState = slotAllocator?.GetFirstUnusedStateMachineState(increasing) ?? firstState; } - public int AllocateState(SyntaxNode awaitOrYieldReturnSyntax) + public StateMachineState AllocateState(SyntaxNode awaitOrYieldReturnSyntax) { Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(awaitOrYieldReturnSyntax)); int direction = _increasing ? +1 : -1; - if (_slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, out var stateNumber) == true) + if (_slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, out var state) == true) { #if DEBUG // two states of the new state machine should not match the same state of the previous machine: - Debug.Assert(!_matchedStates[stateNumber * direction]); - _matchedStates[stateNumber * direction] = true; + Debug.Assert(!_matchedStates[(int)state * direction]); + _matchedStates[(int)state * direction] = true; #endif _matchedStateCount++; } else { - stateNumber = _nextState; + state = _nextState; _nextState += direction; } - return stateNumber; + return state; } /// diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 0ef6f322a3bd2..9138deb880e85 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -384,7 +384,7 @@ protected BoundExpression MakeCurrentThreadId() /// /// Generate the GetEnumerator() method for iterators and GetAsyncEnumerator() for async-iterators. /// - protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSymbol getEnumeratorMethod, ref BoundExpression managedThreadId, int initialState) + protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSymbol getEnumeratorMethod, ref BoundExpression managedThreadId, StateMachineState initialState) { // Produces: // {StateMachineType} result; @@ -437,7 +437,7 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy makeIterator = F.If( // if (this.state == -2 && this.initialThreadId == Thread.CurrentThread.ManagedThreadId) condition: F.LogicalAnd( - F.IntEqual(F.Field(F.This(), stateField), F.Literal(StateMachineStates.FinishedState)), + F.IntEqual(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState)), F.IntEqual(F.Field(F.This(), initialThreadIdField), managedThreadId)), thenClause: F.Block(thenBuilder.ToImmutableAndFree()), elseClauseOpt: makeIterator); @@ -486,7 +486,7 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy /// /// Generate logic to reset the current instance (rather than creating a new instance) /// - protected virtual void GenerateResetInstance(ArrayBuilder builder, int initialState) + protected virtual void GenerateResetInstance(ArrayBuilder builder, StateMachineState initialState) { builder.Add( // this.state = {initialState}; diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineStates.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineStates.cs deleted file mode 100644 index 2f17bf33c8c4b..0000000000000 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineStates.cs +++ /dev/null @@ -1,39 +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.CSharp -{ - internal static class StateMachineStates - { - internal const int FirstIteratorFinalizeState = -3; - - internal const int FinishedState = -2; - internal const int NotStartedOrRunningState = -1; - internal const int FirstUnusedState = 0; - - /// - /// First state in async state machine that is used to resume the machine after await. - /// - internal const int FirstResumableAsyncState = 0; - - internal const int InitialIteratorState = 0; - - /// - /// First state in iterator state machine that is used to resume the machine after yield return. - /// Initial state is not used to resume state machine that yielded. - /// - internal const int FirstResumableIteratorState = InitialIteratorState + 1; - - /// - /// Used for async-iterators to distinguish initial state from so that DisposeAsync can throw in latter case. - /// - internal const int InitialAsyncIteratorState = -3; - - /// - /// First state in async iterator state machine that is used to resume the machine after yield return. - /// Initial state is not used to resume state machine that yielded. - /// - internal const int FirstResumableAsyncIteratorState = InitialAsyncIteratorState - 1; - } -} diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index e42d43436cfdb..761d756887d6f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -125,7 +125,7 @@ internal BoundExpression MakeInvocationExpression( private sealed class SyntheticBinderImpl : BuckStopsHereBinder { private readonly SyntheticBoundNodeFactory _factory; - internal SyntheticBinderImpl(SyntheticBoundNodeFactory factory) : base(factory.Compilation) + internal SyntheticBinderImpl(SyntheticBoundNodeFactory factory) : base(factory.Compilation, associatedSyntaxTree: null) { _factory = factory; } @@ -658,6 +658,9 @@ public BoundLiteral Literal(int value) return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32)) { WasCompilerGenerated = true }; } + public BoundLiteral Literal(StateMachineState value) + => Literal((int)value); + public BoundLiteral Literal(uint value) { return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_UInt32)) { WasCompilerGenerated = true }; @@ -954,26 +957,23 @@ public BoundSpillSequence SpillSequence(ImmutableArray locals, Immu /// /// An internal helper class for building a switch statement. /// - internal class SyntheticSwitchSection + internal readonly struct SyntheticSwitchSection { public readonly ImmutableArray Values; public readonly ImmutableArray Statements; - public SyntheticSwitchSection(ImmutableArray Values, ImmutableArray Statements) + + public SyntheticSwitchSection(ImmutableArray values, ImmutableArray statements) { - this.Values = Values; - this.Statements = Statements; + Values = values; + Statements = statements; } } public SyntheticSwitchSection SwitchSection(int value, params BoundStatement[] statements) - { - return new SyntheticSwitchSection(ImmutableArray.Create(value), ImmutableArray.Create(statements)); - } + => SwitchSection(ImmutableArray.Create(value), statements); - public SyntheticSwitchSection SwitchSection(List values, params BoundStatement[] statements) - { - return new SyntheticSwitchSection(ImmutableArray.CreateRange(values), ImmutableArray.Create(statements)); - } + public SyntheticSwitchSection SwitchSection(ImmutableArray values, params BoundStatement[] statements) + => new(values, ImmutableArray.Create(statements)); /// /// Produce an int switch. diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 5d25277ad7c14..b16fda396c9e7 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -1167,6 +1167,8 @@ internal static DeclarationModifiers GetModifier(SyntaxKind kind, SyntaxKind con return DeclarationModifiers.Required; case SyntaxKind.ScopedKeyword: return DeclarationModifiers.Scoped; + case SyntaxKind.FileKeyword: + return DeclarationModifiers.File; } goto default; @@ -1248,8 +1250,18 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors, bool fo break; } + case DeclarationModifiers.File: + if ((!IsFeatureEnabled(MessageID.IDS_FeatureFileTypes) || forTopLevelStatements) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) + { + return; + } + + // LangVersion errors for 'file' modifier are given during binding. + modTok = ConvertToKeyword(EatToken()); + break; + case DeclarationModifiers.Async: - if (!ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(parsingStatementNotDeclaration: false)) + if (!ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) { return; } @@ -1263,7 +1275,7 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors, bool fo // machinery to make a conservative guess as to whether the user meant required to be a keyword, so that they get a good langver // diagnostic and all the machinery to upgrade their project kicks in. The only exception to this rule is top level statements, // where the user could conceivably have a local named required. For these locations, we need to disambiguate as well. - if ((!IsFeatureEnabled(MessageID.IDS_FeatureRequiredMembers) || forTopLevelStatements) && !ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(parsingStatementNotDeclaration: false)) + if ((!IsFeatureEnabled(MessageID.IDS_FeatureRequiredMembers) || forTopLevelStatements) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) { return; } @@ -1273,7 +1285,7 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors, bool fo break; case DeclarationModifiers.Scoped: - if (!ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(parsingStatementNotDeclaration: false)) + if (!ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) { return; } @@ -1308,16 +1320,16 @@ bool isStructOrRecordKeyword(SyntaxToken token) } } - private bool ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(bool parsingStatementNotDeclaration) + private bool ShouldContextualKeywordBeTreatedAsModifier(bool parsingStatementNotDeclaration) { - Debug.Assert(this.CurrentToken.ContextualKind is SyntaxKind.AsyncKeyword or SyntaxKind.RequiredKeyword or SyntaxKind.ScopedKeyword); + Debug.Assert(this.CurrentToken.Kind == SyntaxKind.IdentifierToken && GetModifier(this.CurrentToken) != DeclarationModifiers.None); // Adapted from CParser::IsAsyncMethod. if (IsNonContextualModifier(PeekToken(1))) { // If the next token is a (non-contextual) modifier keyword, then this token is - // definitely the async or required keyword + // definitely a modifier return true; } @@ -1328,7 +1340,7 @@ private bool ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(bool parsingStatem try { - this.EatToken(); //move past contextual 'async' or 'required' + this.EatToken(); //move past contextual token if (!parsingStatementNotDeclaration && (this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword)) @@ -1336,13 +1348,12 @@ private bool ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(bool parsingStatem this.EatToken(); // "partial" doesn't affect our decision, so look past it. } - // Comment directly from CParser::IsAsyncMethod. - // ... 'async' [partial] ... - // ... 'async' [partial] ... - // ... 'async' [partial] ... - // ... 'async' [partial] ... - // ... 'async' [partial] ... - // ... 'async' [partial] ... + // ... 'TOKEN' [partial] ... + // ... 'TOKEN' [partial] ... + // ... 'TOKEN' [partial] ... + // ... 'TOKEN' [partial] ... + // ... 'TOKEN' [partial] ... + // ... 'TOKEN' [partial] ... // DEVNOTE: Although we parse async user defined conversions, operators, etc. here, // anything other than async methods are detected as erroneous later, during the define phase // The comments in general were not updated to add "async or required" everywhere to preserve @@ -1361,56 +1372,56 @@ private bool ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(bool parsingStatem if (ScanType() != ScanTypeFlags.NotType) { - // We've seen "async TypeName". Now we have to determine if we should we treat - // 'async' as a modifier. Or is the user actually writing something like - // "public async Goo" where 'async' is actually the return type. + // We've seen "TOKEN TypeName". Now we have to determine if we should we treat + // 'TOKEN' as a modifier. Or is the user actually writing something like + // "public TOKEN Goo" where 'TOKEN' is actually the return type. if (IsPossibleMemberName()) { - // we have: "async Type X" or "async Type this", 'async' is definitely a + // we have: "TOKEN Type X" or "TOKEN Type this", 'TOKEN' is definitely a // modifier here. return true; } var currentTokenKind = this.CurrentToken.Kind; - // The file ends with "async TypeName", it's not legal code, and it's much + // The file ends with "TOKEN TypeName", it's not legal code, and it's much // more likely that this is meant to be a modifier. if (currentTokenKind == SyntaxKind.EndOfFileToken) { return true; } - // "async TypeName }". In this case, we just have an incomplete member, and - // we should definitely default to 'async' being considered a return type here. + // "TOKEN TypeName }". In this case, we just have an incomplete member, and + // we should definitely default to 'TOKEN' being considered a return type here. if (currentTokenKind == SyntaxKind.CloseBraceToken) { return true; } - // "async TypeName void". In this case, we just have an incomplete member before - // an existing member. Treat this 'async' as a keyword. + // "TOKEN TypeName void". In this case, we just have an incomplete member before + // an existing member. Treat this 'TOKEN' as a keyword. if (SyntaxFacts.IsPredefinedType(this.CurrentToken.Kind)) { return true; } - // "async TypeName public". In this case, we just have an incomplete member before - // an existing member. Treat this 'async' as a keyword. + // "TOKEN TypeName public". In this case, we just have an incomplete member before + // an existing member. Treat this 'TOKEN' as a keyword. if (IsNonContextualModifier(this.CurrentToken)) { return true; } - // "async TypeName class". In this case, we just have an incomplete member before - // an existing type declaration. Treat this 'async' as a keyword. + // "TOKEN TypeName class". In this case, we just have an incomplete member before + // an existing type declaration. Treat this 'TOKEN' as a keyword. if (IsTypeDeclarationStart()) { return true; } - // "async TypeName namespace". In this case, we just have an incomplete member before - // an existing namespace declaration. Treat this 'async' as a keyword. + // "TOKEN TypeName namespace". In this case, we just have an incomplete member before + // an existing namespace declaration. Treat this 'TOKEN' as a keyword. if (currentTokenKind == SyntaxKind.NamespaceKeyword) { return true; @@ -2688,7 +2699,7 @@ static bool isAcceptableNonDeclarationStatement(StatementSyntax statement, bool private bool IsMisplacedModifier(SyntaxListBuilder modifiers, SyntaxList attributes, TypeSyntax type, out MemberDeclarationSyntax result) { if (GetModifier(this.CurrentToken) != DeclarationModifiers.None && - this.CurrentToken.ContextualKind is not (SyntaxKind.PartialKeyword or SyntaxKind.AsyncKeyword or SyntaxKind.RequiredKeyword or SyntaxKind.ScopedKeyword) && + this.CurrentToken.ContextualKind is not (SyntaxKind.PartialKeyword or SyntaxKind.AsyncKeyword or SyntaxKind.RequiredKeyword or SyntaxKind.ScopedKeyword or SyntaxKind.FileKeyword) && IsComplete(type)) { var misplacedModifier = this.CurrentToken; @@ -8099,13 +8110,13 @@ private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel) tk = this.CurrentToken.ContextualKind; if (tk == SyntaxKind.ScopedKeyword && - ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(parsingStatementNotDeclaration: true)) + ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: true)) { return true; } var isPossibleAttributeOrModifier = (IsAdditionalLocalFunctionModifier(tk) || tk == SyntaxKind.OpenBracketToken) - && (tk != SyntaxKind.AsyncKeyword || ShouldAsyncOrRequiredOrScopedBeTreatedAsModifier(parsingStatementNotDeclaration: true)); + && (tk != SyntaxKind.AsyncKeyword || ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: true)); if (isPossibleAttributeOrModifier) { return true; diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 623654949ba86..67616943ddd73 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +*REMOVED*override Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.GetHashCode() -> int Microsoft.CodeAnalysis.CSharp.Conversion.ConstrainedToType.get -> Microsoft.CodeAnalysis.ITypeSymbol? Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterpolatedMultiLineRawStringStartToken = 9073 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterpolatedRawStringEndToken = 9074 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind @@ -5,6 +6,7 @@ Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterpolatedSingleLineRawStringStartTok Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiLineRawStringLiteralToken = 8519 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.RequiredKeyword = 8447 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.ScopedKeyword = 8448 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +Microsoft.CodeAnalysis.CSharp.SyntaxKind.FileKeyword = 8449 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineRawStringLiteralToken = 8518 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax! parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax? initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax! body) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax! *REMOVED*static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax! parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax! initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax! body) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax! diff --git a/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs b/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs index ea12b763d81a2..80136e313667c 100644 --- a/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs +++ b/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs @@ -21,6 +21,9 @@ private CSharpSyntaxHelper() public override bool IsCaseSensitive => true; + public override int AttributeListKind + => (int)SyntaxKind.AttributeList; + public override bool IsValidIdentifier(string name) => SyntaxFacts.IsValidIdentifier(name); @@ -60,38 +63,50 @@ public override bool IsLambdaExpression(SyntaxNode node) public override SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node) => ((NameSyntax)node).GetUnqualifiedName().Identifier; - public override void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + public override void AddAliases(GreenNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) { - if (node is CompilationUnitSyntax compilationUnit) + if (node is Syntax.InternalSyntax.CompilationUnitSyntax compilationUnit) { AddAliases(compilationUnit.Usings, aliases, global); } - else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration) + else if (node is Syntax.InternalSyntax.BaseNamespaceDeclarationSyntax namespaceDeclaration) { AddAliases(namespaceDeclaration.Usings, aliases, global); } else { - throw ExceptionUtilities.UnexpectedValue(node.Kind()); + throw ExceptionUtilities.UnexpectedValue(node.KindText); } } - private static void AddAliases(SyntaxList usings, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + private static void AddAliases( + CodeAnalysis.Syntax.InternalSyntax.SyntaxList usings, + ArrayBuilder<(string aliasName, string symbolName)> aliases, + bool global) { foreach (var usingDirective in usings) { if (usingDirective.Alias is null) continue; - if (global != usingDirective.GlobalKeyword.Kind() is SyntaxKind.GlobalKeyword) + if (global != (usingDirective.GlobalKeyword != null)) continue; var aliasName = usingDirective.Alias.Name.Identifier.ValueText; - var symbolName = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText; + var symbolName = GetUnqualifiedName(usingDirective.Name).Identifier.ValueText; aliases.Add((aliasName, symbolName)); } } + private static Syntax.InternalSyntax.SimpleNameSyntax GetUnqualifiedName(Syntax.InternalSyntax.NameSyntax name) + => name switch + { + Syntax.InternalSyntax.AliasQualifiedNameSyntax alias => alias.Name, + Syntax.InternalSyntax.QualifiedNameSyntax qualified => qualified.Right, + Syntax.InternalSyntax.SimpleNameSyntax simple => simple, + _ => throw ExceptionUtilities.UnexpectedValue(name.KindText), + }; + public override void AddAliases(CompilationOptions compilation, ArrayBuilder<(string aliasName, string symbolName)> aliases) { // C# doesn't have global aliases at the compilation level. diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index aeecef424f270..6bad689cf0e76 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -6,9 +6,11 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.SymbolDisplay; using Roslyn.Utilities; @@ -179,6 +181,30 @@ public override void VisitNamedType(INamedTypeSymbol symbol) { VisitNamedTypeWithoutNullability(symbol); AddNullableAnnotations(symbol); + + if ((format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes) != 0 + && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } internalSymbol }) + { + var fileDescription = getDisplayFileName(tree) is { Length: not 0 } path + ? path + : $""; + + builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "@")); + builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, fileDescription)); + } + + static string getDisplayFileName(SyntaxTree tree) + { + if (tree.FilePath.Length == 0) + { + return ""; + } + + var pooledBuilder = PooledStringBuilder.GetInstance(); + var sb = pooledBuilder.Builder; + GeneratedNames.AppendFileName(tree.FilePath, sb); + return pooledBuilder.ToStringAndFree(); + } } private void VisitNamedTypeWithoutNullability(INamedTypeSymbol symbol) diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs index 36e8bb11c2d01..ca8f87f34c1f7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs @@ -73,6 +73,8 @@ internal sealed override bool MangleName get { return false; } } + internal sealed override SyntaxTree? AssociatedSyntaxTree => null; + public sealed override int Arity { get { return 0; } diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs index 7f335aea333b8..db12a912c9cfa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs @@ -237,6 +237,8 @@ internal sealed override bool MangleName get { return this.Arity > 0; } } + internal sealed override SyntaxTree? AssociatedSyntaxTree => null; + internal sealed override ImmutableArray TypeArgumentsWithAnnotationsNoUseSiteDiagnostics { get { return GetTypeParametersAsTypeArguments(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs index 286394b84209e..c786c186d65ce 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs @@ -578,6 +578,8 @@ internal override bool MangleName get { return _originalDefinition.MangleName; } } + internal override SyntaxTree? AssociatedSyntaxTree => _originalDefinition.AssociatedSyntaxTree; + internal override DiagnosticInfo? ErrorInfo { get { return _originalDefinition.ErrorInfo; } diff --git a/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs index 08ce38645e2c7..1f141ab0f5a38 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs @@ -145,6 +145,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public override Symbol? ContainingSymbol { get diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 10850e7a37030..11b8ccaf7c630 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -376,6 +376,8 @@ internal abstract override bool MangleName get; } + internal override SyntaxTree AssociatedSyntaxTree => null; + internal abstract int MetadataArity { get; diff --git a/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs index 2dfb36efb2f81..f81ebf3f77af2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs @@ -47,6 +47,9 @@ internal override bool MangleName return mangleName; } } + + internal override SyntaxTree? AssociatedSyntaxTree => null; + /// /// Get the arity of the missing type. /// diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 9238ff07212a2..ba0392ec81bbb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -479,6 +479,7 @@ public virtual bool IsImplicitClass /// public abstract override string Name { get; } +#nullable enable /// /// Return the name including the metadata arity suffix. /// @@ -486,14 +487,30 @@ public override string MetadataName { get { - return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity) : Name; + var fileIdentifier = this.AssociatedFileIdentifier(); + // If we have a fileIdentifier, the type will definitely use CLS arity encoding for nonzero arity. + Debug.Assert(!(fileIdentifier != null && !MangleName && Arity > 0)); + return fileIdentifier != null || MangleName + ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, fileIdentifier) + : Name; } } + /// + /// If this type is a file type, returns the syntax tree where this type is visible. Otherwise, returns null. + /// + internal abstract SyntaxTree? AssociatedSyntaxTree { get; } +#nullable disable + /// /// Should the name returned by Name property be mangled with [`arity] suffix in order to get metadata name. /// Must return False for a type with Arity == 0. /// + /// + /// Some types with Arity > 0 still have MangleName == false. For example, EENamedTypeSymbol. + /// Note that other differences between source names and metadata names exist and are not controlled by this property, + /// such as the 'AssociatedFileIdentifier' prefix for file types. + /// internal abstract bool MangleName { // Intentionally no default implementation to force consideration of appropriate implementation for each new subclass @@ -577,17 +594,9 @@ private void CalculateRequiredMembersIfRequired() private bool TryCalculateRequiredMembers(ref ImmutableSegmentedDictionary.Builder? requiredMembersBuilder) { var lazyRequiredMembers = _lazyRequiredMembers; - if (_lazyRequiredMembers == RequiredMembersErrorSentinel) + if (lazyRequiredMembers == RequiredMembersErrorSentinel) { - if (lazyRequiredMembers.IsDefault) - { - return false; - } - else - { - requiredMembersBuilder = lazyRequiredMembers.ToBuilder(); - return true; - } + return false; } if (BaseTypeNoUseSiteDiagnostics?.TryCalculateRequiredMembers(ref requiredMembersBuilder) == false) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index c6f9cfe5caaac..5271e6e4a4ec2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -2,11 +2,14 @@ // 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.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; @@ -243,6 +246,7 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted Debug.Assert(!emittedTypeName.IsNull); NamespaceOrTypeSymbol scope = this; + Debug.Assert(scope is not MergedNamespaceSymbol); if (scope.Kind == SymbolKind.ErrorType) { @@ -326,6 +330,35 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } Done: + if (isTopLevel + && scope is not PENamespaceSymbol + && (emittedTypeName.ForcedArity == -1 || emittedTypeName.ForcedArity == emittedTypeName.InferredArity) + && GeneratedNameParser.TryParseFileTypeName( + emittedTypeName.UnmangledTypeName, + out string? displayFileName, + out int ordinal, + out string? sourceName)) + { + // also do a lookup for file types from source. + namespaceOrTypeMembers = scope.GetTypeMembers(sourceName); + foreach (var named in namespaceOrTypeMembers) + { + if (named.AssociatedSyntaxTree is SyntaxTree tree + && getDisplayName(tree) == displayFileName + && named.DeclaringCompilation.GetSyntaxTreeOrdinal(tree) == ordinal + && named.Arity == emittedTypeName.InferredArity) + { + if ((object?)namedType != null) + { + namedType = null; + break; + } + + namedType = named; + } + } + } + if ((object?)namedType == null) { if (isTopLevel) @@ -339,6 +372,13 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } return namedType; + + static string getDisplayName(SyntaxTree tree) + { + var sb = PooledStringBuilder.GetInstance(); + GeneratedNames.AppendFileName(tree.FilePath, sb); + return sb.ToStringAndFree(); + } } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs index 0b578b6715240..a1b600de31315 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs @@ -49,6 +49,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public AssemblySymbol EmbeddingAssembly { get diff --git a/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs index f8247bddef230..6161835bfa505 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs @@ -41,6 +41,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public NamedTypeSymbol UnderlyingSymbol { get diff --git a/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs index c50c29cbe0fad..2b31203cb7a28 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs @@ -73,6 +73,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public string Guid { get diff --git a/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs index 3c8eb1aa884d2..3178fe12f7762 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs @@ -48,6 +48,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + internal override DiagnosticInfo? ErrorInfo { get diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs index f5173fe3d8665..6f09cef0e4714 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs @@ -194,6 +194,8 @@ INamedTypeSymbol INamedTypeSymbol.TupleUnderlyingType bool INamedTypeSymbol.IsSerializable => UnderlyingNamedTypeSymbol.IsSerializable; + bool INamedTypeSymbol.IsFile => UnderlyingNamedTypeSymbol.AssociatedSyntaxTree is not null; + INamedTypeSymbol INamedTypeSymbol.NativeIntegerUnderlyingType => UnderlyingNamedTypeSymbol.NativeIntegerUnderlyingType.GetPublicSymbol(); #region ISymbol Members diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs index 169f4b8b1da7a..86180610ecd95 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs @@ -103,6 +103,11 @@ internal static DeclarationModifiers CheckModifiers( modifierErrors |= checkFeature(DeclarationModifiers.PrivateProtected, MessageID.IDS_FeaturePrivateProtected) | checkFeature(DeclarationModifiers.Required, MessageID.IDS_FeatureRequiredMembers); + if ((result & DeclarationModifiers.File) != 0) + { + modifierErrors |= !Binder.CheckFeatureAvailability(errorLocation.SourceTree, MessageID.IDS_FeatureFileTypes, diagnostics, errorLocation); + } + return result; bool checkFeature(DeclarationModifiers modifier, MessageID featureID) @@ -316,6 +321,8 @@ internal static string ConvertSingleModifierToSyntaxText(DeclarationModifiers mo return SyntaxFacts.GetText(SyntaxKind.RequiredKeyword); case DeclarationModifiers.Scoped: return SyntaxFacts.GetText(SyntaxKind.ScopedKeyword); + case DeclarationModifiers.File: + return SyntaxFacts.GetText(SyntaxKind.FileKeyword); default: throw ExceptionUtilities.UnexpectedValue(modifier); } @@ -367,6 +374,8 @@ private static DeclarationModifiers ToDeclarationModifier(SyntaxKind kind) return DeclarationModifiers.Required; case SyntaxKind.ScopedKeyword: return DeclarationModifiers.Scoped; + case SyntaxKind.FileKeyword: + return DeclarationModifiers.File; default: throw ExceptionUtilities.UnexpectedValue(kind); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs index 56a79864e1e76..49d06f3996bf4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs @@ -73,6 +73,7 @@ protected sealed override void MethodChecks(BindingDiagnosticBag diagnostics) } this.CheckEffectiveAccessibility(_lazyReturnType, _lazyParameters, diagnostics); + this.CheckFileTypeUsage(_lazyReturnType, _lazyParameters, diagnostics); if (_lazyIsVararg && (IsGenericMethod || ContainingType.IsGenericType || _lazyParameters.Length > 0 && _lazyParameters[_lazyParameters.Length - 1].IsParams)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs index f229cde04f7a5..987d3a7e93160 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs @@ -96,6 +96,12 @@ internal static void AddDelegateMembers( diagnostics.Add(ErrorCode.ERR_BadVisDelegateReturn, delegateType.Locations[0], delegateType, invoke.ReturnType); } + var delegateTypeIsFile = delegateType.IsFileTypeOrUsesFileTypes(); + if (!delegateTypeIsFile && invoke.ReturnType.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, delegateType.Locations[0], invoke.ReturnType, delegateType); + } + for (int i = 0; i < invoke.Parameters.Length; i++) { var parameterSymbol = invoke.Parameters[i]; @@ -104,6 +110,10 @@ internal static void AddDelegateMembers( // Inconsistent accessibility: parameter type '{1}' is less accessible than delegate '{0}' diagnostics.Add(ErrorCode.ERR_BadVisDelegateParam, delegateType.Locations[0], delegateType, parameterSymbol.Type); } + else if (!delegateTypeIsFile && parameterSymbol.Type.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, delegateType.Locations[0], parameterSymbol.Type, delegateType); + } } diagnostics.Add(delegateType.Locations[0], useSiteInfo); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 6652539dc7891..eb9c4c3be96b4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -274,7 +274,9 @@ private DeclarationModifiers MakeModifiers(TypeKind typeKind, BindingDiagnosticB { Symbol containingSymbol = this.ContainingSymbol; DeclarationModifiers defaultAccess; - var allowedModifiers = DeclarationModifiers.AccessibilityMask; + + // note: we give a specific diagnostic when a file type is nested + var allowedModifiers = DeclarationModifiers.AccessibilityMask | DeclarationModifiers.File; if (containingSymbol.Kind == SymbolKind.Namespace) { @@ -414,6 +416,10 @@ private DeclarationModifiers MakeAndCheckTypeModifiers( { result |= defaultAccess; } + else if ((result & DeclarationModifiers.File) != 0) + { + diagnostics.Add(ErrorCode.ERR_FileTypeNoExplicitAccessibility, Locations[0], this); + } if (missingPartial) { @@ -471,7 +477,8 @@ internal static void ReportReservedTypeName(string? name, CSharpCompilation comp } if (reportIfContextual(SyntaxKind.RecordKeyword, MessageID.IDS_FeatureRecords, ErrorCode.WRN_RecordNamedDisallowed) - || reportIfContextual(SyntaxKind.RequiredKeyword, MessageID.IDS_FeatureRequiredMembers, ErrorCode.ERR_RequiredNameDisallowed)) + || reportIfContextual(SyntaxKind.RequiredKeyword, MessageID.IDS_FeatureRequiredMembers, ErrorCode.ERR_RequiredNameDisallowed) + || reportIfContextual(SyntaxKind.FileKeyword, MessageID.IDS_FeatureFileTypes, ErrorCode.ERR_FileTypeNameDisallowed)) { return; } @@ -819,6 +826,10 @@ internal override ManagedKind GetManagedKind(ref CompoundUseSiteInfo HasFlag(DeclarationModifiers.New); + internal bool IsFile => HasFlag(DeclarationModifiers.File); + + internal sealed override SyntaxTree? AssociatedSyntaxTree => IsFile ? declaration.Declarations[0].Location.SourceTree : null; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool HasFlag(DeclarationModifiers flag) => (_declModifiers & flag) != 0; @@ -1262,7 +1273,7 @@ private Dictionary> GetTypeMembersDictio private Dictionary> MakeTypeMembers(BindingDiagnosticBag diagnostics) { var symbols = ArrayBuilder.GetInstance(); - var conflictDict = new Dictionary<(string, int), SourceNamedTypeSymbol>(); + var conflictDict = new Dictionary<(string name, int arity, SyntaxTree? syntaxTree), SourceNamedTypeSymbol>(); try { foreach (var childDeclaration in declaration.Children) @@ -1270,7 +1281,7 @@ private Dictionary> MakeTypeMembers(Bind var t = new SourceNamedTypeSymbol(this, childDeclaration, diagnostics); this.CheckMemberNameDistinctFromType(t, diagnostics); - var key = (t.Name, t.Arity); + var key = (t.Name, t.Arity, t.AssociatedSyntaxTree); SourceNamedTypeSymbol? other; if (conflictDict.TryGetValue(key, out other)) { @@ -1733,6 +1744,11 @@ protected void AfterMembersChecks(BindingDiagnosticBag diagnostics) } } + if (IsFile && (object?)ContainingType != null) + { + diagnostics.Add(ErrorCode.ERR_FileTypeNested, location, this); + } + return; bool hasBaseTypeOrInterface(Func predicate) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs index 0148108694fce..791b88c38b64d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs @@ -46,7 +46,11 @@ protected sealed override DeclarationModifiers Modifiers protected void TypeChecks(TypeSymbol type, BindingDiagnosticBag diagnostics) { - if (type.IsStatic) + if (type.IsFileTypeOrUsesFileTypes() && !ContainingType.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, this.ErrorLocation, type, ContainingType); + } + else if (type.IsStatic) { // Cannot declare a variable of static type '{0}' diagnostics.Add(ErrorCode.ERR_VarDeclIsStaticClass, this.ErrorLocation, type); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs index 6d84ea23d1b5b..225c383d4f001 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs @@ -278,6 +278,27 @@ protected void CheckEffectiveAccessibility(TypeWithAnnotations returnType, Immut diagnostics.Add(Locations[0], useSiteInfo); } + protected void CheckFileTypeUsage(TypeWithAnnotations returnType, ImmutableArray parameters, BindingDiagnosticBag diagnostics) + { + if (ContainingType.IsFileTypeOrUsesFileTypes()) + { + return; + } + + if (returnType.Type.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, Locations[0], returnType.Type, ContainingType); + } + + foreach (var param in parameters) + { + if (param.Type.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, Locations[0], param.Type, ContainingType); + } + } + } + protected void MakeFlags( MethodKind methodKind, DeclarationModifiers declarationModifiers, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs index 2f98d0400d534..93fe92389fec6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs @@ -384,6 +384,11 @@ static bool containsOnlyOblivious(TypeSymbol type) // Inconsistent accessibility: base class '{1}' is less accessible than class '{0}' diagnostics.Add(ErrorCode.ERR_BadVisBaseClass, baseTypeLocation, this, baseType); } + + if (baseType.IsFileTypeOrUsesFileTypes() && !this.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeBase, baseTypeLocation, baseType, this); + } } var baseInterfacesRO = baseInterfaces.ToImmutableAndFree(); @@ -396,6 +401,11 @@ static bool containsOnlyOblivious(TypeSymbol type) // Inconsistent accessibility: base interface '{1}' is less accessible than interface '{0}' diagnostics.Add(ErrorCode.ERR_BadVisBaseInterface, interfaceLocations[i], this, i); } + + if (i.IsFileTypeOrUsesFileTypes() && !this.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeBase, interfaceLocations[i], i, this); + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs index a3d202bebc10e..b1098e3ea273f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs @@ -757,6 +757,11 @@ UsingsAndDiagnostics buildUsings( else { var importedType = (NamedTypeSymbol)imported; + if (usingDirective.GlobalKeyword != default(SyntaxToken) && importedType.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_GlobalUsingStaticFileType, usingDirective.Name.Location, imported); + } + if (!getOrCreateUniqueUsings(ref uniqueUsings, globalUsingNamespacesOrTypes).Add(importedType)) { diagnostics.Add(!globalUsingNamespacesOrTypes.IsEmpty && getOrCreateUniqueGlobalUsingsNotInTree(ref uniqueGlobalUsings, globalUsingNamespacesOrTypes, declarationSyntax.SyntaxTree).Contains(imported) ? diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs index a1b1c4e391b10..5bba097c91837 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs @@ -372,13 +372,17 @@ private static void CheckMembers(NamespaceSymbol @namespace, Dictionary (object)loc.SourceTree == leftTree, leftTree)) + { + return false; + } + + return true; + } } private NamespaceOrTypeSymbol BuildSymbol(MergedNamespaceOrTypeDeclaration declaration, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs index 5b541f2af2fe0..c952b7a10b125 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs @@ -46,6 +46,7 @@ public sealed override bool ReturnsVoid this.SetReturnsVoid(_lazyReturnType.IsVoidType()); this.CheckEffectiveAccessibility(_lazyReturnType, _lazyParameters, diagnostics); + this.CheckFileTypeUsage(_lazyReturnType, _lazyParameters, diagnostics); var location = locations[0]; // Checks taken from MemberDefiner::defineMethod diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 4f18ea1b33a84..781b6bac6a5c7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -451,6 +451,11 @@ private TypeWithAnnotations ComputeType(Binder binder, SyntaxNode syntax, Bindin diagnostics.Add((this.IsIndexer ? ErrorCode.ERR_BadVisIndexerReturn : ErrorCode.ERR_BadVisPropertyType), Location, this, type.Type); } + if (type.Type.IsFileTypeOrUsesFileTypes() && !ContainingType.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, Location, type.Type, ContainingType); + } + diagnostics.Add(Location, useSiteInfo); if (type.IsVoidType()) @@ -528,6 +533,10 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, { diagnostics.Add(ErrorCode.ERR_BadVisIndexerParam, Location, this, param.Type); } + else if (param.Type.IsFileTypeOrUsesFileTypes() && !this.ContainingType.IsFileTypeOrUsesFileTypes()) + { + diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, Location, param.Type, this.ContainingType); + } else if (SetMethod is object && param.Name == ParameterSymbol.ValueParameterName) { diagnostics.Add(ErrorCode.ERR_DuplicateGeneratedName, param.Locations.FirstOrDefault() ?? Location, param.Name); diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs index 4f1a4d2545fea..a589243a8359f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs @@ -873,7 +873,7 @@ public virtual string GetDocumentationCommentXml( SymbolDisplayFormat.TestFormat .AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions.IncludeNotNullableReferenceTypeModifier) - .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); + .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes); internal virtual string GetDebuggerDisplay() { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs index 66a294e654bad..1404bc8a80496 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs @@ -30,6 +30,7 @@ internal enum GeneratedNameKind ReusableHoistedLocalField = '7', LambdaCacheField = '9', FixedBufferField = 'e', + FileType = 'F', AnonymousType = 'f', TransparentIdentifier = 'h', AnonymousTypeField = 'i', diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs index b9d6d4b44fa23..55f6d2b85c572 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Text.RegularExpressions; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -170,5 +171,35 @@ internal static bool TryParseAnonymousTypeParameterName(string typeParameterName propertyName = null; return false; } + + // A full metadata name for a generic file type looks like: + // FN__ClassName`A + // where 'N' is the syntax tree ordinal, 'A' is the arity, + // and 'ClassName' is the source name of the type. + // + // The "unmangled" name of a generic file type looks like: + // FN__ClassName + private static readonly Regex s_fileTypeOrdinalPattern = new Regex(@"<([a-zA-Z_0-9]*)>F(\d)+__", RegexOptions.Compiled); + + /// + /// This method will work with either unmangled or mangled type names as input, but it does not remove any arity suffix if present. + /// + internal static bool TryParseFileTypeName(string generatedName, [NotNullWhen(true)] out string? displayFileName, out int ordinal, [NotNullWhen(true)] out string? originalTypeName) + { + if (s_fileTypeOrdinalPattern.Match(generatedName) is Match { Success: true, Groups: var groups, Index: var index, Length: var length } + && int.TryParse(groups[2].Value, out ordinal)) + { + displayFileName = groups[1].Value; + + var prefixEndsAt = index + length; + originalTypeName = generatedName.Substring(prefixEndsAt); + return true; + } + + ordinal = -1; + displayFileName = null; + originalTypeName = null; + return false; + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs index cf9acb4bcd6e0..2466b3268be33 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs @@ -26,14 +26,14 @@ internal static string MakeBackingFieldName(string propertyName) return "<" + propertyName + ">k__BackingField"; } - internal static string MakeIteratorFinallyMethodName(int finalizeState) + internal static string MakeIteratorFinallyMethodName(StateMachineState finalizeState) { - Debug.Assert(finalizeState < -2); + Debug.Assert((int)finalizeState < -2); // It is important that the name is only derived from the finalizeState, so that when // editing method during EnC the Finally methods corresponding to matching states have matching names. Debug.Assert((char)GeneratedNameKind.IteratorFinallyMethod == 'm'); - return "<>m__Finally" + StringExtensions.GetNumeral(-(finalizeState + 2)); + return "<>m__Finally" + StringExtensions.GetNumeral(-((int)finalizeState + 2)); } internal static string MakeStaticLambdaDisplayClassName(int methodOrdinal, int generation) @@ -494,5 +494,38 @@ internal static string LambdaCopyParameterName(int ordinal) { return ""; } + + internal static string MakeFileIdentifier(string filePath, int ordinal) + { + var pooledBuilder = PooledStringBuilder.GetInstance(); + var sb = pooledBuilder.Builder; + sb.Append('<'); + AppendFileName(filePath, sb); + sb.Append('>'); + sb.Append((char)GeneratedNameKind.FileType); + sb.Append(ordinal); + sb.Append("__"); + return pooledBuilder.ToStringAndFree(); + } + + internal static void AppendFileName(string? filePath, StringBuilder sb) + { + var fileName = FileNameUtilities.GetFileName(filePath, includeExtension: false); + if (fileName is null) + { + return; + } + + foreach (var ch in fileName) + { + sb.Append(ch switch + { + >= 'a' and <= 'z' => ch, + >= 'A' and <= 'Z' => ch, + >= '0' and <= '9' => ch, + _ => '_' + }); + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs index dd677239e1078..12ec75a9c91ca 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs @@ -165,6 +165,10 @@ internal override IEnumerable GetFieldsToEmit() internal override bool MangleName => Arity > 0; +#nullable enable + internal sealed override SyntaxTree? AssociatedSyntaxTree => null; +#nullable disable + public override bool IsImplicitlyDeclared => true; internal override bool ShouldAddWinRTMembers => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs index 72a47ac9eccea..9e7e9370e689c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs @@ -99,6 +99,8 @@ public SynthesizedEmbeddedAttributeSymbolBase( internal override bool MangleName => false; + internal override SyntaxTree AssociatedSyntaxTree => null; + internal override bool HasCodeAnalysisEmbeddedAttribute => true; internal override bool IsInterpolatedStringHandlerType => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 70fb7b7600ebd..69d0a214d5b41 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -453,7 +453,7 @@ internal override BoundBlock CreateBody(BindingDiagnosticBag diagnostics) // Creates a new top-level binder that just contains the global imports for the compilation. // The imports are required if a consumer of the scripting API is using a Task implementation // that uses extension methods. - Binder binder = WithUsingNamespacesAndTypesBinder.Create(compilation.GlobalImports, next: new BuckStopsHereBinder(compilation), withImportChainEntry: true); + Binder binder = WithUsingNamespacesAndTypesBinder.Create(compilation.GlobalImports, next: new BuckStopsHereBinder(compilation, null), withImportChainEntry: true); binder = new InContainerBinder(compilation.GlobalNamespace, binder); var ctor = _containingType.GetScriptConstructor(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs index 1dda4db66ed28..50b6d3fc9634c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs @@ -219,10 +219,10 @@ private ExecutableCodeBinder CreateBodyBinder(bool ignoreAccessibility) { CSharpCompilation compilation = DeclaringCompilation; - Binder result = new BuckStopsHereBinder(compilation); + var syntaxNode = SyntaxNode; + Binder result = new BuckStopsHereBinder(compilation, syntaxNode.SyntaxTree); var globalNamespace = compilation.GlobalNamespace; var declaringSymbol = (SourceNamespaceSymbol)compilation.SourceModule.GlobalNamespace; - var syntaxNode = SyntaxNode; result = WithExternAndUsingAliasesBinder.Create(declaringSymbol, syntaxNode, WithUsingNamespacesAndTypesBinder.Create(declaringSymbol, syntaxNode, result)); result = new InContainerBinder(globalNamespace, result); result = new InContainerBinder(ContainingType, result); diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 2f4f42572a34c..c754ed3038577 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -1360,6 +1361,22 @@ public static bool IsPartial(this TypeSymbol type) return type is SourceNamedTypeSymbol { IsPartial: true }; } + public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) + { + var foundType = type.VisitType(predicate: (type, _, _) => type is SourceMemberContainerTypeSymbol { IsFile: true }, arg: (object?)null); + return foundType is not null; + } + + internal static string? AssociatedFileIdentifier(this NamedTypeSymbol type) + { + if (type.AssociatedSyntaxTree is not SyntaxTree tree) + { + return null; + } + var ordinal = type.DeclaringCompilation.GetSyntaxTreeOrdinal(tree); + return GeneratedNames.MakeFileIdentifier(tree.FilePath, ordinal); + } + public static bool IsPointerType(this TypeSymbol type) { return type is PointerTypeSymbol; diff --git a/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs b/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs index 30e4a67d87744..1ee9dbccbb271 100644 --- a/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs +++ b/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs @@ -91,6 +91,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + internal override DiagnosticInfo ErrorInfo { get diff --git a/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs index e4ca579dc1b86..80b81912ae070 100644 --- a/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs @@ -37,5 +37,7 @@ internal override bool MangleName return false; } } + + internal override SyntaxTree? AssociatedSyntaxTree => null; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs index 48a0389a946be..aa26a0c66f5c8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs @@ -95,6 +95,8 @@ internal override bool MangleName } } + internal override SyntaxTree AssociatedSyntaxTree => _underlyingType.AssociatedSyntaxTree; + public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken)) { return _underlyingType.GetDocumentationCommentXml(preferredCulture, expandIncludes, cancellationToken); diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs index 6a6b48dbe3346..0310f5a0bc92a 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs @@ -111,7 +111,7 @@ internal bool HasReferenceDirectives { Debug.Assert(HasCompilationUnitRoot); - return Options.Kind == SourceCodeKind.Script && GetCompilationUnitRoot().GetReferenceDirectives().Count > 0; + return Options.Kind == SourceCodeKind.Script && GetCompilationUnitRoot().HasReferenceDirectives; } } @@ -124,7 +124,7 @@ internal bool HasReferenceOrLoadDirectives if (Options.Kind == SourceCodeKind.Script) { var compilationUnitRoot = GetCompilationUnitRoot(); - return compilationUnitRoot.GetReferenceDirectives().Count > 0 || compilationUnitRoot.GetLoadDirectives().Count > 0; + return compilationUnitRoot.HasReferenceDirectives || compilationUnitRoot.HasLoadDirectives; } return false; diff --git a/src/Compilers/CSharp/Portable/Syntax/CompilationUnitSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/CompilationUnitSyntax.cs index c7093242604ea..cb387fd8d7954 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CompilationUnitSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CompilationUnitSyntax.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Syntax { @@ -19,9 +20,12 @@ public IList GetReferenceDirectives() internal IList GetReferenceDirectives(Func? filter) { + if (!this.ContainsDirectives) + return SpecializedCollections.EmptyList(); + // #r directives are always on the first token of the compilation unit. var firstToken = (SyntaxNodeOrToken)this.GetFirstToken(includeZeroWidth: true); - return firstToken.GetDirectives(filter); + return firstToken.GetDirectives(filter); } /// @@ -29,11 +33,43 @@ internal IList GetReferenceDirectives(Func public IList GetLoadDirectives() { + if (!this.ContainsDirectives) + return SpecializedCollections.EmptyList(); + // #load directives are always on the first token of the compilation unit. var firstToken = (SyntaxNodeOrToken)this.GetFirstToken(includeZeroWidth: true); return firstToken.GetDirectives(filter: null); } + internal bool HasReferenceDirectives + // #r and #load directives are always on the first token of the compilation unit. + => HasFirstTokenDirective(static n => n is ReferenceDirectiveTriviaSyntax); + + internal bool HasLoadDirectives + // #r and #load directives are always on the first token of the compilation unit. + => HasFirstTokenDirective(static n => n is LoadDirectiveTriviaSyntax); + + private bool HasFirstTokenDirective(Func predicate) + { + if (this.ContainsDirectives) + { + var firstToken = this.GetFirstToken(includeZeroWidth: true); + if (firstToken.ContainsDirectives) + { + foreach (var trivia in firstToken.LeadingTrivia) + { + if (trivia.GetStructure() is { } structure && + predicate(structure)) + { + return true; + } + } + } + } + + return false; + } + internal Syntax.InternalSyntax.DirectiveStack GetConditionalDirectivesStack() { IEnumerable directives = this.GetDirectives(filter: IsActiveConditionalDirective); diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index 99d3d48bf3055..8b7d6a6ffce69 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -408,6 +408,8 @@ public enum SyntaxKind : ushort RequiredKeyword = 8447, /// Represents . ScopedKeyword = 8448, + /// Represents . + FileKeyword = 8449, // when adding a contextual keyword following functions must be adapted: // diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index db242c0548924..d6624d597b04b 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -1138,7 +1138,7 @@ public static SyntaxKind GetPreprocessorKeywordKind(string text) public static IEnumerable GetContextualKeywordKinds() { - for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.ScopedKeyword; i++) + for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.FileKeyword; i++) { yield return (SyntaxKind)i; } @@ -1193,6 +1193,7 @@ public static bool IsContextualKeyword(SyntaxKind kind) case SyntaxKind.UnmanagedKeyword: case SyntaxKind.RequiredKeyword: case SyntaxKind.ScopedKeyword: + case SyntaxKind.FileKeyword: return true; default: return false; @@ -1316,6 +1317,8 @@ public static SyntaxKind GetContextualKeywordKind(string text) return SyntaxKind.RequiredKeyword; case "scoped": return SyntaxKind.ScopedKeyword; + case "file": + return SyntaxKind.FileKeyword; default: return SyntaxKind.None; } @@ -1759,6 +1762,8 @@ public static string GetText(SyntaxKind kind) return "required"; case SyntaxKind.ScopedKeyword: return "scoped"; + case SyntaxKind.FileKeyword: + return "file"; default: return string.Empty; } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 5a6afdb48d939..5770ce3656616 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -582,6 +582,31 @@ Obor názvů pro celý soubor musí předcházet všem ostatním členům v souboru. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Pevné pole nesmí být pole ref. @@ -627,6 +652,11 @@ Globální direktiva using musí předcházet všem direktivám using, které nejsou globální. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Příkaz goto nemůže přejít na místo před deklarací using ve stejném bloku. @@ -1602,6 +1632,11 @@ obor názvů pro celý soubor + + file types + file types + + generic attributes obecné atributy @@ -1719,7 +1754,7 @@ pattern matching ReadOnly/Span<char> on constant string - vzor odpovídající ReadOnly/Span<char> na konstantním řetězci + pattern matching ReadOnly/Span<char> on constant string @@ -2250,7 +2285,7 @@ -instrument:TestCoverage Vytvoří sestavení instrumentované ke shromažďování informací o pokrytí. -sourcelink:<file> Informace o zdrojovém odkazu vkládané do souboru PDB.. - + - CHYBY A UPOZORNĚNÍ - -warnaserror[+|-] Hlásí všechna upozornění jako chyby. -warnaserror[+|-]:<warn list> Hlásí zadaná upozornění jako chyby. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index a3a937628f574..444d59211af86 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -582,6 +582,31 @@ Der Dateibereichsnamespace muss allen anderen Elementen in einer Datei vorangestellt sein. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Ein festes Feld darf kein Referenzfeld sein. @@ -627,6 +652,11 @@ Eine globale using-Anweisung muss allen nicht globalen using-Anweisungen vorangehen. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Mit "goto" kann nicht an eine Position vor einer using-Deklaration im selben Block gesprungen werden. @@ -1602,6 +1632,11 @@ Dateibereichsnamespace + + file types + file types + + generic attributes Generische Attribute @@ -2253,7 +2288,7 @@ – FEHLER UND WARNUNGEN – -warnaserror[+|-] Meldet alle Warnungen als Fehler. --warnaserror[+|-]:<Warnungsliste> Meldet bestimmte Warnungen als Fehler +-warnaserror[+|-]:<Warnungsliste> Meldet bestimmte Warnungen als Fehler (Verwendung von "nullable" für alle Warnungen zur NULL-Zulässigkeit). -warn:<n> Legt die Warnstufe fest (0 oder höher) (Kurzform: -w). -nowarn:<Warnungsliste> Deaktiviert bestimmte Warnmeldungen diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 7ee07dfb7c046..a6750e71a41fe 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -582,6 +582,31 @@ El espacio de nombres con ámbito de archivo debe preceder a todos los demás miembros de un archivo. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Un campo fijo no debe ser un campo de referencia. @@ -627,6 +652,11 @@ Una directiva de uso global debe ser anterior a todas las directivas no globales que no son de uso. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Una instrucción goto no puede saltar a una ubicación antes que una declaración using dentro del mismo bloque. @@ -1602,6 +1632,11 @@ espacio de nombres con ámbito de archivo + + file types + file types + + generic attributes atributos genéricos @@ -2188,7 +2223,7 @@ Opciones del compilador de Visual C# - ARCHIVOS DE SALIDA - --out:<archivo> Especifica el nombre del archivo de salida (el valor predeterminado: nombre base del +-out:<archivo> Especifica el nombre del archivo de salida (el valor predeterminado: nombre base del archivo con la clase principal o el primer archivo) -target:exe Compila un archivo ejecutable de consola (predeterminado) (forma corta: -t:exe) @@ -2250,7 +2285,7 @@ -instrument:TestCoverage Produce un ensamblado instrumentado para recopilar información de cobertura. -sourcelink:<archivo> Información del vínculo de origen para insertar en el PDB. - + - ERRORES Y ADVERTENCIAS - -warnaserror[+|-] Notifica todas las advertencias como errores. -warnaserror[+|-]:<lista de advertencias > Notifica advertencias específicas como errores @@ -2322,7 +2357,7 @@ -pdb:<archivo> Especifica el nombre de archivo de información de depuración (valor predeterminado: nombre de archivo de salida con la extensión .pdb). -errorendlocation Línea y columna de salida de la ubicación final de - cada error. + cada error. -preferreduilang Especifica el nombre del lenguaje de salida preferido. -nosdkpath Deshabilita la búsqueda de la ruta del SDK predeterminada para los ensamblados de biblioteca estándar. -nostdlib[+|-] No hace referencia a la biblioteca estándar (mscorlib.dll). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 099e7a3548855..f8cabe2e0ac9f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -582,6 +582,31 @@ Un espace de noms de portée de fichier doit précéder tous les autres membres d’un fichier. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Un champ fixe ne doit pas être un champ ref. @@ -627,6 +652,11 @@ Une directive using globale doit précéder toutes les directives using non globales. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Un goto ne peut pas accéder à un emplacement avant une déclaration using dans le même bloc. @@ -1602,6 +1632,11 @@ espace de noms inclus dans l'étendue de fichier + + file types + file types + + generic attributes attributs génériques @@ -2250,7 +2285,7 @@ -instrument:TestCoverage Produire un assembly instrumenté pour collecter les informations de couverture -sourcelink:<fichier> Informations du lien source à incorporer dans le fichier PDB. - + - ERREURS ET AVERTISSEMENTS - -warnaserror[+|-] Signaler tous les avertissements comme des erreurs -warnaserror[+|-]:<avertiss.> Signaler des avertissements spécifiques comme des erreurs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 073f3e8f981dd..3b51629e1f333 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -582,6 +582,31 @@ Lo spazio dei nomi con ambito file deve precedere tutti gli altri membri di un file. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Un campo fisso non deve essere un campo ref. @@ -627,6 +652,11 @@ Una direttiva sull'utilizzo globale deve precedere tutte le direttiva non sull'uso non globale. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Un'istruzione goto non può passare a una posizione che precede una dichiarazione using all'interno dello stesso blocco. @@ -1602,6 +1632,11 @@ spazio dei nomi con ambito file + + file types + file types + + generic attributes attributi generici @@ -2213,7 +2248,7 @@ ai caratteri jolly specificati -reference:<alias>=<file> Crea un riferimento ai metadati dal file di assembly specificato usando l'alias indicato. Forma breve: -r --reference:<elenco file> Crea un riferimento ai metadati dai file di assembly +-reference:<elenco file> Crea un riferimento ai metadati dai file di assembly specificati. Forma breve: -r -addmodule:<elenco file> Collega i moduli specificati in questo assembly -link:<elenco file> Incorpora metadati dai file di assembly di @@ -2250,7 +2285,7 @@ -instrument:TestCoverage Produce un assembly instrumentato per raccogliere informazioni sul code coverage -sourcelink:<file> Informazioni sul collegamento all'origine da incorporare nel file PDB. - + - ERRORI E AVVISI - -warnaserror[+|-] Segnala tutti gli avvisi come errori -warnaserror[+|-]:<elenco avvisi> Segnala determinati avvisi come errori @@ -2304,7 +2339,7 @@ - AVANZATE - -baseaddress:<indirizzo> Indirizzo di base della libreria da compilare --checksumalgorithm:<alg> Consente di specificare l'algoritmo per calcolare il checksum +-checksumalgorithm:<alg> Consente di specificare l'algoritmo per calcolare il checksum del file di origine archiviato nel file PDB. I valori supportati sono: SHA1 o SHA256 (impostazione predefinita). -codepage:<n> Consente di specificare la tabella codici da usare all'apertura dei file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 42fd64861810b..123bba4708685 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -582,6 +582,31 @@ ファイルスコープの名前空間は、ファイル内の他のすべてのメンバーの前に指定する必要があります。 + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. 固定フィールドを ref フィールドにすることはできません。 @@ -627,6 +652,11 @@ グローバル using ディレクティブは、すべての非グローバル using ディレクティブの前に指定する必要があります。 + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. goto は同じブロック内の using 宣言より前の位置にはジャンプできません。 @@ -1602,6 +1632,11 @@ ファイルスコープの名前空間 + + file types + file types + + generic attributes 汎用属性 @@ -2250,7 +2285,7 @@ -instrument:TestCoverage カバレッジ情報を収集するようにインストルメント化された アセンブリを生成します -sourcelink:<file> PDB に埋め込むソース リンク情報。 - + - エラーと警告 - -warnaserror[+|-] すべての警告をエラーとして報告します -warnaserror[+|-]:<warn list> 特定の警告をエラーとして報告します diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index f0de1fa32baa4..11eabd58ffafb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -582,6 +582,31 @@ 파일 범위 네임스페이스는 파일의 다른 모든 멤버보다 앞에 와야 합니다. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. 고정 필드는 참조 필드가 아니어야 합니다. @@ -627,6 +652,11 @@ 전역 using 지시문은 전역이 아닌 모든 using 지시문 앞에 있어야 합니다. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. goto는 동일한 블록 내의 using 선언 앞 위치로 이동할 수 없습니다. @@ -1602,6 +1632,11 @@ 파일 범위 네임스페이스 + + file types + file types + + generic attributes 제네릭 특성 @@ -2196,7 +2231,7 @@ -target:library 라이브러리를 빌드합니다. (약식: -t:library) -target:module 다른 어셈블리에 추가할 수 있는 모듈을 빌드합니다. (약식: -t:module) --target:appcontainerexe Appcontainer 실행 파일을 빌드합니다. (약식: +-target:appcontainerexe Appcontainer 실행 파일을 빌드합니다. (약식: -t:appcontainerexe) -target:winmdobj WinMDExp에서 사용되는 Windows 런타임 중간 파일을 빌드합니다. (약식: -t:winmdobj) @@ -2310,7 +2345,7 @@ 지정합니다. -utf8output 컴파일러 메시지를 UTF-8 인코딩으로 출력합니다. -main:<type> 진입점이 포함된 형식을 지정합니다(다른 - 모든 가능한 진입점 무시). + 모든 가능한 진입점 무시). (약식: -m) -fullpaths 컴파일러가 정규화된 경로를 생성합니다. -filealign:<n> 출력 파일 섹션에 사용되는 맞춤을 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index d6857ecf45e47..c0b7021280bc3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -582,6 +582,31 @@ Przestrzeń nazw z określonym zakresem plików musi poprzedzać wszystkie inne składowe w pliku. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Pole stałe nie może być polem referencyjnym. @@ -627,6 +652,11 @@ Globalne używające dyrektywy muszą poprzedzać wszystkie nieglobalne używające dyrektywy. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Instrukcja goto nie może przechodzić do lokalizacji występującej przed deklaracją using w tym samym bloku. @@ -1602,6 +1632,11 @@ przestrzeń nazw z określonym zakresem plików + + file types + file types + + generic attributes atrybuty ogólne @@ -1719,7 +1754,7 @@ pattern matching ReadOnly/Span<char> on constant string - dopasowanie wzorca ReadOnly/Span<char> w ciągu stałym + pattern matching ReadOnly/Span<char> on constant string @@ -2188,35 +2223,35 @@ Opcje kompilatora Visual C# - PLIKI WYJŚCIOWE - --out:<plik> Określ nazwę pliku wyjściowego (domyślnie: nazwa podstawowa +-out:<plik> Określ nazwę pliku wyjściowego (domyślnie: nazwa podstawowa pliku z klasą główną lub pierwszego pliku) --target:exe Kompiluj plik wykonywalny konsoli (domyślnie) (krótka +-target:exe Kompiluj plik wykonywalny konsoli (domyślnie) (krótka wersja: -t:exe) --target:winexe Kompiluj plik wykonywalny systemu Windows (krótka wersja: +-target:winexe Kompiluj plik wykonywalny systemu Windows (krótka wersja: -t:winexe) -target:library Kompiluj bibliotekę (krótka wersja: -t:library) --target:module Kompiluj moduł, który można dodać do innego +-target:module Kompiluj moduł, który można dodać do innego zestawu (krótka wersja: -t:module) --target:appcontainerexe Kompiluj plik wykonywalny kontenera aplikacji (krótka wersja: +-target:appcontainerexe Kompiluj plik wykonywalny kontenera aplikacji (krótka wersja: -t:appcontainerexe) --target:winmdobj Kompiluj plik pośredni środowiska uruchomieniowego systemu Windows +-target:winmdobj Kompiluj plik pośredni środowiska uruchomieniowego systemu Windows przeznaczony dla narzędzia WinMDExp (krótka wersja: -t:winmdobj) -doc:<plik> Plik dokumentacji XML do wygenerowania -refout:<plik> Dane wyjściowe zestawu odwołania do wygenerowania -platform:<ciąg> Ogranicz platformy, na których można uruchamiać ten kod: x86, - Itanium, x64, arm, arm64, anycpu32bitpreferred lub + Itanium, x64, arm, arm64, anycpu32bitpreferred lub anycpu. Wartość domyślna to anycpu. - PLIKI WEJŚCIOWE - --recurse:<symbol wieloznaczny> Uwzględnij wszystkie pliki zawarte w bieżącym katalogu i - podkatalogach zgodnie ze specyfikacją określoną przy użyciu +-recurse:<symbol wieloznaczny> Uwzględnij wszystkie pliki zawarte w bieżącym katalogu i + podkatalogach zgodnie ze specyfikacją określoną przy użyciu symboli wieloznacznych --reference:<alias>=<plik> Odwołuj się do metadanych z określonego pliku +-reference:<alias>=<plik> Odwołuj się do metadanych z określonego pliku zestawu przy użyciu podanego aliasu (krótka wersja: -r) --reference:<lista_plików> Odwołuj się do metadanych z określonych +-reference:<lista_plików> Odwołuj się do metadanych z określonych plików zestawów (krótka wersja: -r) -addmodule:<lista plików> Połącz określone moduły z tym zestawem --link:<lista_plików> Osadź metadane z określonych plików +-link:<lista_plików> Osadź metadane z określonych plików zestawów międzyoperacyjnych (krótka wersja: -l) -analyzer:<lista_plików> Uruchom analizatory z tego zestawu (krótka wersja: -a) @@ -2232,16 +2267,16 @@ -win32manifest:<plik> Określ plik manifestu środowiska Win32 (xml) -nowin32manifest Nie dołączaj domyślnego manifestu środowiska Win32 -resource:<informacje_o_zasobie> Osadź określony zasób (krótka wersja: -res) --linkresource:<informacje_o_zasobie> Połącz określony zasób z tym zestawem - (krótka wersja: -linkres), gdzie format informacji o zasobie +-linkresource:<informacje_o_zasobie> Połącz określony zasób z tym zestawem + (krótka wersja: -linkres), gdzie format informacji o zasobie to <plik>[,<nazwa ciągu>[,public|private]] - GENEROWANIE KODU - -debug[+|-] Emituj informacje o debugowaniu -debug:{full|pdbonly|portable|embedded} - Określ typ debugowania (wartość domyślna to „full”, + Określ typ debugowania (wartość domyślna to „full”, wartość „portable” to format międzyplatformowy, - a wartość „embedded” to format międzyplatformowy wbudowany w + a wartość „embedded” to format międzyplatformowy wbudowany w docelowym pliku dll lub exe) -optimize[+|-] Włącz optymalizacje (krótka wersja: -o) -deterministic Utwórz zestaw deterministyczny @@ -2268,11 +2303,11 @@ -reportanalyzer Zgłaszaj dodatkowe informacje analizatora, takie jak czas wykonywania. -skipanalyzers[+|-] Pomiń wykonywanie analizatorów diagnostycznych. - + -JĘZYK - -checked[+|-] Generuj operacje sprawdzenia przepełnienia -unsafe[+|-] Zezwalaj na niebezpieczny kod --define:<lista symboli> Zdefiniuj symbole kompilacji warunkowej (krótka +-define:<lista symboli> Zdefiniuj symbole kompilacji warunkowej (krótka wersja: -d) -langversion:? Wyświetl dozwolone wartości dla wersji języka -langversion:<ciąg> Określ wersję języka, na przykład @@ -2286,7 +2321,7 @@ Określ opcję kontekstu dopuszczającego wartość null: enable|disable|warnings|annotations. - ZABEZPIECZENIA - --delaysign[+|-] Podpisz z opóźnieniem zestaw, używając tylko +-delaysign[+|-] Podpisz z opóźnieniem zestaw, używając tylko części publicznej klucza o silnej nazwie -publicsign[+|-] Podpisz publicznie zestaw, używając tylko części publicznej klucza o silnej nazwie @@ -2307,34 +2342,34 @@ -checksumalgorithm:<algorytm> Określ algorytm do obliczania sumy kontrolnej pliku źródłowego przechowywanej w pliku PDB. Obsługiwane wartości: SHA1 lub SHA256 (domyślnie). --codepage:<n> Określ stronę kodową do użycia podczas otwierania +-codepage:<n> Określ stronę kodową do użycia podczas otwierania plików źródłowych -utf8output Wyprowadź komunikaty kompilatora przy użyciu kodowania UTF-8 --main:<typ> Określ typ zawierający punkt wejścia - (zignoruj wszystkie pozostałe możliwe punkty wejścia) (krótka +-main:<typ> Określ typ zawierający punkt wejścia + (zignoruj wszystkie pozostałe możliwe punkty wejścia) (krótka wersja: -m) -fullpaths Kompilator generuje w pełni kwalifikowane ścieżki --filealign:<n> Określ wyrównanie stosowane dla sekcji +-filealign:<n> Określ wyrównanie stosowane dla sekcji plików wyjściowych --pathmap:<K1>=<W1>,<K2>=<W2>,... - Określ mapowanie dla nazw ścieżek źródłowych wyprowadzanych przez +-pathmap:<K1>=<W1>,<K2>=<W2>,... + Określ mapowanie dla nazw ścieżek źródłowych wyprowadzanych przez kompilator. --pdb:<plik> Określ nazwę pliku z informacjami o debugowaniu (domyślnie: +-pdb:<plik> Określ nazwę pliku z informacjami o debugowaniu (domyślnie: nazwa pliku wyjściowego z rozszerzeniem pdb) --errorendlocation Wyprowadź wiersz i kolumnę lokalizacji końcowej dla +-errorendlocation Wyprowadź wiersz i kolumnę lokalizacji końcowej dla każdego błędu -preferreduilang Określ nazwę preferowanego języka wyjściowego. -nosdkpath Wyłącz przeszukiwanie domyślnej ścieżki zestawu SDK dla zestawów biblioteki standardowej. -nostdlib[+|-] Nie odwołuj się do biblioteki standardowej (mscorlib.dll) -subsystemversion:<ciąg> Określ wersję podsystemu tego zestawu --lib:<lista plików> Określ dodatkowe katalogi do przeszukania pod kątem +-lib:<lista plików> Określ dodatkowe katalogi do przeszukania pod kątem odwołań --errorreport:<ciąg> Określ, w jaki sposób obsługiwać wewnętrzne błędy kompilatora: - prompt, send, queue lub none. Wartość domyślna to +-errorreport:<ciąg> Określ, w jaki sposób obsługiwać wewnętrzne błędy kompilatora: + prompt, send, queue lub none. Wartość domyślna to queue. --appconfig:<plik> Określ plik konfiguracji aplikacji +-appconfig:<plik> Określ plik konfiguracji aplikacji zawierający ustawienia powiązania zestawu --moduleassemblyname:<ciąg> Nazwa zestawu, którego częścią +-moduleassemblyname:<ciąg> Nazwa zestawu, którego częścią ma być ten moduł -modulename:<ciąg> Określ nazwę modułu źródłowego -generatedfilesout:<katalog> Umieść pliki wygenerowane podczas kompilacji diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index ef31a0dab8339..2df658dd0785b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -582,6 +582,31 @@ O namespace de escopo de arquivo deve preceder todos os outros membros em um arquivo. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Um campo fixo não deve ser um campo ref. @@ -627,6 +652,11 @@ Uma diretiva de uso global deve preceder todas as diretivas de uso não global. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Um goto não pode saltar para um local antes de uma declaração using no mesmo bloco. @@ -1602,6 +1632,11 @@ namespace de escopo de arquivo + + file types + file types + + generic attributes atributos genéricos @@ -2213,7 +2248,7 @@ curinga -reference:<alias>=<file> Metadados de referência do arquivo de assembly especificado usando o alias fornecido (Forma abreviada: -r) --reference:<file list> Metadados de referência dos arquivos de assembly +-reference:<file list> Metadados de referência dos arquivos de assembly especificados (Forma abreviada: -r) -addmodule:<file list> Vincular o módulo especificado a este assembly -link:<file list> Inserir os metadados dos arquivos de assembly de @@ -2241,7 +2276,7 @@ -debug:{full|pdbonly|portable|embedded} Especificar o tipo de depuração ('full' é o padrão, 'portable' é um formato multiplataforma, - 'embedded' é um formato multiplataforma inserido no + 'embedded' é um formato multiplataforma inserido no .dll ou no .exe de destino) -optimize[+|-] Habilitar as otimizações (Forma abreviada: -o) -deterministic Produzir um assembly determinístico @@ -2275,7 +2310,7 @@ -define:<symbol list> Definir os símbolos de compilação condicional (Forma abreviada: -d) -langversion:? Exibir os valores permitidos para a versão da linguagem --langversion:<string> Especificar a versão da linguagem, como +-langversion:<string> Especificar a versão da linguagem, como `latest` (última versão, incluindo as versões secundárias), `default` (igual a `latest`), `latestmajor` (última versão, excluindo as versões secundárias), @@ -2292,7 +2327,7 @@ da chave de nome forte -keyfile:<file> Especificar a arquivo de chave de nome forte -keycontainer:<string> Especificar o contêiner de chave de nome forte --highentropyva[+|-] Habilitar a ASLR de alta entropia +-highentropyva[+|-] Habilitar a ASLR de alta entropia – DIVERSOS – @<file> Ler o arquivo de resposta de mais opções diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index f39ffc68a656c..9b5596df5b316 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -582,6 +582,31 @@ Пространство имен с файловой областью должно быть раньше всех остальных элементов в файле. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Фиксированное поле не должно быть полем ref. @@ -627,6 +652,11 @@ Глобальная директива using должна предшествовать всем неглобальным директивам using. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Оператор goto не может переходить к расположению раньше объявления using в том же блоке. @@ -1602,6 +1632,11 @@ пространство имен с файловой областью + + file types + file types + + generic attributes универсальные атрибуты @@ -2250,7 +2285,7 @@ -instrument:TestCoverage Создать сборку, инструментированную для сбора сведений об объеме протестированного кода -sourcelink:<file> Данные о ссылке на исходные файлы для внедрения в PDB. - + — Ошибки и предупреждения - -warnaserror[+|-] Регистрировать все предупреждения как ошибки -warnaserror[+|-]:<warn list> Регистрировать указанные предупреждения как ошибки diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 78180faf2b2ba..45c5c7e1ede97 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -582,6 +582,31 @@ Dosya kapsamlı ad alanı bir dosyadaki diğer tüm üyelerin önünde olmalıdır. + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. Sabit bir alan bir başvuru alanı olamaz. @@ -627,6 +652,11 @@ Genel bir using yönergesi genel olmayan tüm using yönergelerinden önce gelmelidir. + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. Bir goto, aynı blok içinde yer alan using bildiriminden önceki bir konuma atlayamaz. @@ -1602,6 +1632,11 @@ dosya kapsamlı ad alanı + + file types + file types + + generic attributes genel öznitelikler diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index ef58c9dfddf4d..4ce3170143b2e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -582,6 +582,31 @@ 文件范围内的命名空间必须位于文件中所有其他成员之前。 + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. 固定字段不能是 ref 字段。 @@ -627,6 +652,11 @@ 全局 using 指令必须位于所有非全局 using 指令之前。 + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. goto 无法跳转到同一块中 using 声明之前的某个位置。 @@ -1602,6 +1632,11 @@ 文件范围内的命名空间 + + file types + file types + + generic attributes 通用属性 @@ -2203,12 +2238,12 @@ (短格式: - t:winmdobj) -doc:<file> 要生成的 XML 文档文件 -refout:<file> 要生成的引用程序集输出 - + -platform:<string> 限制可以运行此代码的平台: x86、 Itanium、x64、arm、arm64、 anycpu32bitpreferred 或 anycpu。默认值是 anycpu。 - - 输入文件 - + - 输入文件 - -recurse:<wildcard> 根据通配符规范包括当前目录和 子目录中的 所有文件 @@ -2222,7 +2257,7 @@ -analyzer:<file list> 从此程序集运行分析器 (短格式: -a) -additionalfile:<file list> 不直接影响代码生成 - 但由分析器用于生成错误或警报 + 但由分析器用于生成错误或警报 的其他文件。 -embed 在 PDB 中嵌入所有源文件。 -embed:<file list> 在 PDB 中嵌入特定文件。 @@ -2235,7 +2270,7 @@ -resource:<resinfo> 嵌入指定的资源(短格式: -res) - linkresource:<resinfo> 将指定资源关联到此程序集 (短格式: -linkres),其中 resinfo 格式 - + 是 <file>[,<string name>[,public|private]] - 代码生成 - @@ -2252,16 +2287,16 @@ -instrument:TestCoverage 生成 已检测的程序集以收集 覆盖范围信息 -sourcelink:<file> 要嵌入 PDB 的源链接信息。 - + - 错误和警报 - -warnaserror[+|-] 将所有警报报告为错误 -warnaserror[+|-]:<warn list> 将特定警报报告为错误 (将 "nullable" 用于所有可为 null 的警报) -warn:<n> 设置警报级别(0 或更高) (短格式: -w) - + -nowarn:<warn list> 禁用特定警报消息 (将 "nullable" 用于所有可为 null 的警报) - + -ruleset:<file> 指定禁用特定诊断的规则集 文件。 -errorlog:<file> [,version=<sarif_version>] @@ -2270,7 +2305,7 @@ sarif_version:{1|2|2.1} 默认值是 1. 2 和 2.1 这两者 都是指 SARIF 版本 2.1.0。 -reportanalyzer 报告其他分析器信息,例如 - 执行时间。 + 执行时间。 -skipanalyzers[+|-] 跳过诊断分析器的执行。 - 语言 - @@ -2311,7 +2346,7 @@ -checksumalgorithm:<alg> 指定算法以计算存储在 PDB 中的源文件 校验和。支持的值是: SHA1 或 SHA256 (默认)。 - + -codepage:<n> 指定打开源文件时使用的 代码页 -utf8output 采用 UTF-8 编码的输出编译器消息 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 3195961056adc..370d8ae45645f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -139,12 +139,12 @@ The first operand of an overloaded shift operator must have the same type as the containing type or its type parameter constrained to it - 多載移位 (Shift) 運算子的第一個運算元的類型必須和包含的類型相同,或是其型別參數受限於該運算子 + The first operand of an overloaded shift operator must have the same type as the containing type or its type parameter constrained to it A static virtual or abstract interface member can be accessed only on a type parameter. - 只能在型別參數上存取靜態虛擬或抽象介面成員。 + A static virtual or abstract interface member can be accessed only on a type parameter. @@ -249,7 +249,7 @@ Cannot convert &method group '{0}' to delegate type '{1}'. - 無法將方法群組 '{0}' 轉換成委派類型 '{1}'(&M)。 + Cannot convert &method group '{0}' to delegate type '{1}'. @@ -304,12 +304,12 @@ This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. - 此建構函式必須新增 'SetsRequiredMembers',因為它鏈結至具有該屬性的建構函式。 + This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. The operator '{0}' requires a matching non-checked version of the operator to also be defined - 運算子 '{0}' 需要也同時定義運算子的相符未檢查版本 + The operator '{0}' requires a matching non-checked version of the operator to also be defined @@ -469,7 +469,7 @@ Do not use 'System.Runtime.CompilerServices.RequiredMemberAttribute'. Use the 'required' keyword on required fields and properties instead. - 請勿使用 'System.Runtime.CompilerServices.RequiredMemberAttribute'。請改為在必要的欄位和屬性上使用 'required' 關鍵字。 + Do not use 'System.Runtime.CompilerServices.RequiredMemberAttribute'. Use the 'required' keyword on required fields and properties instead. @@ -489,7 +489,7 @@ An expression tree may not contain an access of static virtual or abstract interface member - 運算式樹狀架構不可包含靜態虛擬或抽象介面成員的存取權 + An expression tree may not contain an access of static virtual or abstract interface member @@ -582,6 +582,31 @@ 以檔為範圍的命名空間必須在檔案中的所有其他成員之前。 + + File type '{0}' cannot be used as a base type of non-file type '{1}'. + File type '{0}' cannot be used as a base type of non-file type '{1}'. + + + + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + + + Types and aliases cannot be named 'file'. + Types and aliases cannot be named 'file'. + + + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + + + File type '{0}' cannot use accessibility modifiers. + File type '{0}' cannot use accessibility modifiers. + + A fixed field must not be a ref field. 修正的欄位不能是 ref 欄位。 @@ -627,6 +652,11 @@ 全域 using 指示詞必須在所有非全域 using 指示詞之前。 + + File type '{0}' cannot be used in a 'global using static' directive. + File type '{0}' cannot be used in a 'global using static' directive. + + A goto cannot jump to a location before a using declaration within the same block. 在相同區塊內,goto 不可跳到 using 宣告前的位置。 @@ -654,7 +684,7 @@ An 'implicit' user-defined conversion operator cannot be declared checked - 無法將「隱式」使用者定義轉換運算子宣告為已檢查 + An 'implicit' user-defined conversion operator cannot be declared checked @@ -889,7 +919,7 @@ Unexpected keyword 'unchecked' - 未預期的關鍵字 'unchecked' + Unexpected keyword 'unchecked' @@ -954,7 +984,7 @@ '{2}' cannot satisfy the 'new()' constraint on parameter '{1}' in the generic type or or method '{0}' because '{2}' has required members. - '{2}' 無法滿足泛型型別或方法 '{0}' 中參數 '{1}' 的 'new()' 限制式,因為 '{2}' 具有必要的成員。 + '{2}' cannot satisfy the 'new()' constraint on parameter '{1}' in the generic type or or method '{0}' because '{2}' has required members. @@ -1074,7 +1104,7 @@ User-defined operator '{0}' cannot be declared checked - 使用者定義的運算子 '{0}' 無法宣告為已檢查 + User-defined operator '{0}' cannot be declared checked @@ -1089,7 +1119,7 @@ '{0}' must be required because it overrides required member '{1}' - '{0}' 必須為必要項目,因為它會覆蓋必要的成員 '{1}' + '{0}' must be required because it overrides required member '{1}' @@ -1104,7 +1134,7 @@ The 'parameter null-checking' feature is not supported. - 不支援 'parameter null-checking' 功能。 + The 'parameter null-checking' feature is not supported. @@ -1154,7 +1184,7 @@ A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. - 不支援字串 'null' 常數做為 '{0}' 的模式。請改為使用空字串。 + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. @@ -1234,7 +1264,7 @@ Ref returning properties cannot be required. - 無法要求 Ref 傳回屬性。 + Ref returning properties cannot be required. @@ -1244,42 +1274,42 @@ Required member '{0}' cannot be hidden by '{1}'. - '{1}' 無法隱藏必要成員 '{0}'。 + Required member '{0}' cannot be hidden by '{1}'. Required member '{0}' cannot be less visible or have a setter less visible than the containing type '{1}'. - 必要成員 '{0}' 可見度不能較低,或 setter 的可見程度低於包含的類型 '{1}'。 + Required member '{0}' cannot be less visible or have a setter less visible than the containing type '{1}'. Required member '{0}' must be set in the object initializer or attribute constructor. - 必須在物件初始設定式或屬性建構函式中設定必要的成員 '{0}'。 + Required member '{0}' must be set in the object initializer or attribute constructor. Required member '{0}' must be settable. - 必要的成員 '{0}' 必須可設定。 + Required member '{0}' must be settable. The required members list for the base type '{0}' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - 基底類型 '{0}' 所需的成員清單格式錯誤,無法解譯。若要使用此建構函式,請套用 'SetsRequiredMembers' 屬性。 + The required members list for the base type '{0}' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. The required members list for '{0}' is malformed and cannot be interpreted. - '{0}' 的必要成員清單格式錯誤,無法解譯。 + The required members list for '{0}' is malformed and cannot be interpreted. Required member '{0}' must be assigned a value, it cannot use a nested member or collection initializer. - 必要的成員 '{0}' 必須指派值,它無法使用巢狀成員或集合初始設定式。 + Required member '{0}' must be assigned a value, it cannot use a nested member or collection initializer. Types and aliases cannot be named 'required'. - 類型和別名不能命名為 'required'。 + Types and aliases cannot be named 'required'. @@ -1494,12 +1524,12 @@ Auto-implemented property '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the property. - 在控制項傳回呼叫者之前,必須先完全指派自動實作屬性 '{0}'。請考慮更新至語言版本 '{1}' 以自動預設屬性。 + Auto-implemented property '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the property. Field '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the field. - 在控制項傳回呼叫者之前,必須先完全指派欄位 '{0}'。請考慮更新至語言版本 '{1}' 以自動預設欄位。 + Field '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the field. @@ -1514,7 +1544,7 @@ '{0}' requires compiler feature '{1}', which is not supported by this version of the C# compiler. - '{0}' 需要編譯器功能 '{1}',此版本的 C# 編譯器不支援此功能。 + '{0}' requires compiler feature '{1}', which is not supported by this version of the C# compiler. @@ -1534,17 +1564,17 @@ Use of possibly unassigned field '{0}'. Consider updating to language version '{1}' to auto-default the field. - 使用可能未指派的欄位 '{0}'。請考慮更新語言版本 '{1}' 以自動預設欄位。 + Use of possibly unassigned field '{0}'. Consider updating to language version '{1}' to auto-default the field. Use of possibly unassigned auto-implemented property '{0}'. Consider updating to language version '{1}' to auto-default the property. - 使用可能未指派的自動實作屬性 '{0}'。請考慮更新語言版本 '{1}' 以自動預設屬性。 + Use of possibly unassigned auto-implemented property '{0}'. Consider updating to language version '{1}' to auto-default the property. The 'this' object cannot be used before all of its fields have been assigned. Consider updating to language version '{0}' to auto-default the unassigned fields. - 在指派 'this' 物件的所有欄位之前,無法使用該物件。請考慮更新語言版本 '{0}',以自動預設未指派的欄位。 + The 'this' object cannot be used before all of its fields have been assigned. Consider updating to language version '{0}' to auto-default the unassigned fields. @@ -1569,12 +1599,12 @@ auto default struct fields - 自動預設結構欄位 + auto default struct fields checked user-defined operators - 已檢查使用者定義的運算子 + checked user-defined operators @@ -1602,6 +1632,11 @@ 以檔案為範圍的命名空間 + + file types + file types + + generic attributes 一般屬性 @@ -1674,12 +1709,12 @@ relaxed shift operator - 寬鬆移位 (Shift) 運算子 + relaxed shift operator required members - 必要成員 + required members @@ -1694,7 +1729,7 @@ unsigned right shift - 未簽署右移位 + unsigned right shift @@ -1719,7 +1754,7 @@ pattern matching ReadOnly/Span<char> on constant string - 常數字串上的模式比對 ReadOnly/Span<char> + pattern matching ReadOnly/Span<char> on constant string @@ -1854,12 +1889,12 @@ Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. - 除非包含的類型已過時或所有建構函式已過時,否則必要成員 '{0}' 的屬性不應為 'ObsoleteAttribute'。 + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Members attributed with 'ObsoleteAttribute' should not be required unless the containing type is obsolete or all constructors are obsolete. - 除非包含的類型已過時或所有建構函式已過時,否則不應要求具有 'ObsoleteAttribute' 屬性的成員。 + Members attributed with 'ObsoleteAttribute' should not be required unless the containing type is obsolete or all constructors are obsolete. @@ -1974,7 +2009,7 @@ 'UnmanagedCallersOnly' can only be applied to ordinary static non-abstract, non-virtual methods or static local functions. - 'UnmanagedCallersOnly' 僅適用於一般靜態非抽象方法、非虛擬方法或靜態區域函式。 + 'UnmanagedCallersOnly' can only be applied to ordinary static non-abstract, non-virtual methods or static local functions. UnmanagedCallersOnly is not localizable. @@ -2250,7 +2285,7 @@ -instrument:TestCoverage 產生經檢測的組件,以收集 涵蓋範圍資訊 -sourcelink:<file> 要內嵌至 PDB 的來源連結資訊。 - + - 錯誤與警告 - -warnaserror[+|-] 將所有警告回報為錯誤 -warnaserror[+|-]:<warn list> 將特定警告回報為錯誤 @@ -3758,42 +3793,42 @@ Control is returned to caller before auto-implemented property '{0}' is explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派自動實作屬性 '{0}' 之前會先將控制項傳回呼叫者,導致先前隱含的指派為 'default'。 + Control is returned to caller before auto-implemented property '{0}' is explicitly assigned, causing a preceding implicit assignment of 'default'. Control is returned to caller before auto-implemented property is explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派自動實作屬性之前會先將控制項傳回呼叫者,導致先前隱含的指派為 'default'。 + Control is returned to caller before auto-implemented property is explicitly assigned, causing a preceding implicit assignment of 'default'. Auto-implemented property '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the property. - 在控制項傳回呼叫者之前,必須先完全指派自動實作屬性 '{0}'。請考慮更新至語言版本 '{1}' 以自動預設屬性。 + Auto-implemented property '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the property. An auto-implemented property must be fully assigned before control is returned to the caller. Consider updating the language version to auto-default the property. - 在控制項傳回呼叫者之前,必須先完全指派自動實作屬性。請考慮更新至語言版本以自動預設屬性。 + An auto-implemented property must be fully assigned before control is returned to the caller. Consider updating the language version to auto-default the property. Control is returned to caller before field '{0}' is explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派欄位 '{0}' 之前會先將控制項傳回呼叫者,導致先前隱含的指派為 'default'。 + Control is returned to caller before field '{0}' is explicitly assigned, causing a preceding implicit assignment of 'default'. Control is returned to caller before field is explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派欄位之前會先將控制項傳回呼叫者,導致先前隱含的指派為 'default'。 + Control is returned to caller before field is explicitly assigned, causing a preceding implicit assignment of 'default'. Field '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the field. - 在控制項傳回呼叫者之前,必須先完全指派欄位 '{0}'。請考慮更新至語言版本 '{1}' 以自動預設欄位。 + Field '{0}' must be fully assigned before control is returned to the caller. Consider updating to language version '{1}' to auto-default the field. Fields of a struct must be fully assigned in a constructor before control is returned to the caller. Consider updating the language version to auto-default the field. - 在控制項傳回呼叫者之前,必須先在建構函式中完全指派結構的欄位。請考慮更新至語言版本以自動預設欄位。 + Fields of a struct must be fully assigned in a constructor before control is returned to the caller. Consider updating the language version to auto-default the field. @@ -3858,22 +3893,22 @@ Field '{0}' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派之前會先讀取欄位 '{0}',導致先前隱含的指派為 'default'。 + Field '{0}' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. Field is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派之前會先讀取欄位,導致先前隱含的指派為 'default'。 + Field is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. Use of possibly unassigned field '{0}'. Consider updating to language version '{1}' to auto-default the field. - 使用可能未指派的欄位 '{0}'。請考慮更新語言版本 '{1}' 以自動預設欄位。 + Use of possibly unassigned field '{0}'. Consider updating to language version '{1}' to auto-default the field. Use of possibly unassigned field. Consider updating the language version to auto-default the field. - 使用可能未指派的欄位。請考慮更新語言版本以自動預設欄位。 + Use of possibly unassigned field. Consider updating the language version to auto-default the field. @@ -3898,22 +3933,22 @@ Auto-implemented property '{0}' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派之前會先讀取自動實作屬性 '{0}',導致先前隱含的指派為 'default'。 + Auto-implemented property '{0}' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. Auto-implemented property is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. - 在明確指派之前會先讀取自動實作屬性,導致先前隱含的指派為 'default'。 + Auto-implemented property is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. Use of possibly unassigned auto-implemented property '{0}'. Consider updating to language version '{1}' to auto-default the property. - 使用可能未指派的自動實作屬性 '{0}'。請考慮更新語言版本 '{1}' 以自動預設屬性。 + Use of possibly unassigned auto-implemented property '{0}'. Consider updating to language version '{1}' to auto-default the property. Use of possibly unassigned auto-implemented property. Consider updating the language version to auto-default the property. - 使用可能未指派的自動實作屬性。請考慮更新語言版本以自動預設屬性。 + Use of possibly unassigned auto-implemented property. Consider updating the language version to auto-default the property. @@ -3923,22 +3958,22 @@ The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. - 在指派 'this' 物件的所有欄位之前,會先讀取該物件,導致先前對未明確指派的欄位進行隱含的 'default' 指派。 + The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. - 在指派 'this' 物件的所有欄位之前,會先讀取該物件,導致先前對未明確指派的欄位進行隱含的 'default' 指派。 + The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. The 'this' object cannot be used before all of its fields have been assigned. Consider updating to language version '{0}' to auto-default the unassigned fields. - 在指派 'this' 物件的所有欄位之前,無法使用該物件。請考慮更新語言版本 '{0}',以自動預設未指派的欄位。 + The 'this' object cannot be used before all of its fields have been assigned. Consider updating to language version '{0}' to auto-default the unassigned fields. The 'this' object cannot be used in a constructor before all of its fields have been assigned. Consider updating the language version to auto-default the unassigned fields. - 在指派 'this' 物件的所有欄位之前,無法在建構函式中使用。請考慮更新語言版本,以自動預設未指派的欄位。 + The 'this' object cannot be used in a constructor before all of its fields have been assigned. Consider updating the language version to auto-default the unassigned fields. @@ -5977,7 +6012,7 @@ If such a class is used as a base class and if the deriving class defines a dest The first operand of an overloaded shift operator must have the same type as the containing type - 多載移位 (Shift) 運算子的第一個運算元的類型必須和包含類型相同 + The first operand of an overloaded shift operator must have the same type as the containing type diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index a341d4ddb3ae1..caa82ebcfbd60 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -5835,11 +5835,7 @@ class A \ Assert.Equal(nameGuid, assemblyName.Name); Assert.Equal("0.0.0.0", assemblyName.Version.ToString()); Assert.Equal(string.Empty, assemblyName.CultureName); -#if NETCOREAPP - Assert.Null(assemblyName.GetPublicKeyToken()); -#else Assert.Equal(Array.Empty(), assemblyName.GetPublicKeyToken()); -#endif } [Fact(Skip = "https://github.com/dotnet/roslyn/issues/55727")] diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 5a29c09b7c7f0..12db7c67d314c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -558,11 +558,11 @@ .locals init (object V_0, IL_000e: unbox.any ""double"" IL_0013: stloc.1 IL_0014: ldloc.1 - IL_0015: ldc.r8 3.14 - IL_001e: beq.s IL_0055 - IL_0020: ldloc.1 - IL_0021: call ""bool double.IsNaN(double)"" - IL_0026: brtrue.s IL_004b + IL_0015: call ""bool double.IsNaN(double)"" + IL_001a: brtrue.s IL_004b + IL_001c: ldloc.1 + IL_001d: ldc.r8 3.14 + IL_0026: beq.s IL_0055 IL_0028: br.s IL_005f IL_002a: ldloc.0 IL_002b: isinst ""float"" @@ -571,11 +571,11 @@ .locals init (object V_0, IL_0033: unbox.any ""float"" IL_0038: stloc.2 IL_0039: ldloc.2 - IL_003a: ldc.r4 3.14 - IL_003f: beq.s IL_005a + IL_003a: call ""bool float.IsNaN(float)"" + IL_003f: brtrue.s IL_0050 IL_0041: ldloc.2 - IL_0042: call ""bool float.IsNaN(float)"" - IL_0047: brtrue.s IL_0050 + IL_0042: ldc.r4 3.14 + IL_0047: beq.s IL_005a IL_0049: br.s IL_005f IL_004b: ldc.i4.1 IL_004c: stloc.s V_4 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs index 23a68895a0263..24ed6c83f40d5 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs @@ -9913,162 +9913,156 @@ static int M(double d) expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M", @" { - // Code size 499 (0x1f3) + // Code size 478 (0x1de) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.0 - IL_0001: ldc.r8 13.1 - IL_000a: blt.un IL_00fb - IL_000f: ldarg.0 - IL_0010: ldc.r8 21.1 - IL_0019: blt.un.s IL_008b + IL_0001: ldc.r8 27.1 + IL_000a: blt.un.s IL_002f + IL_000c: ldarg.0 + IL_000d: ldc.r8 29.1 + IL_0016: blt IL_017e IL_001b: ldarg.0 - IL_001c: ldc.r8 27.1 - IL_0025: blt.un.s IL_004a - IL_0027: ldarg.0 - IL_0028: ldc.r8 29.1 - IL_0031: blt IL_0193 - IL_0036: ldarg.0 - IL_0037: ldc.r8 29.1 - IL_0040: beq IL_01b3 - IL_0045: br IL_01ef - IL_004a: ldarg.0 - IL_004b: ldc.r8 23.1 - IL_0054: blt IL_01a9 - IL_0059: ldarg.0 - IL_005a: ldc.r8 25.1 - IL_0063: blt IL_01d3 - IL_0068: ldarg.0 - IL_0069: ldc.r8 25.1 - IL_0072: beq IL_01e1 - IL_0077: ldarg.0 - IL_0078: ldc.r8 26.1 - IL_0081: beq IL_0198 - IL_0086: br IL_01ef - IL_008b: ldarg.0 - IL_008c: ldc.r8 16.1 - IL_0095: blt.un.s IL_00d8 - IL_0097: ldarg.0 - IL_0098: ldc.r8 18.1 - IL_00a1: blt IL_01ce - IL_00a6: ldarg.0 - IL_00a7: ldc.r8 18.1 - IL_00b0: beq IL_01d8 - IL_00b5: ldarg.0 - IL_00b6: ldc.r8 19.1 - IL_00bf: beq IL_01ae - IL_00c4: ldarg.0 - IL_00c5: ldc.r8 20.1 - IL_00ce: beq IL_01e6 - IL_00d3: br IL_01ef - IL_00d8: ldarg.0 - IL_00d9: ldc.r8 15.1 - IL_00e2: blt IL_01b8 - IL_00e7: ldarg.0 - IL_00e8: ldc.r8 15.1 - IL_00f1: beq IL_01c1 - IL_00f6: br IL_01ef - IL_00fb: ldarg.0 - IL_00fc: ldc.r8 7.1 - IL_0105: blt.un.s IL_015f - IL_0107: ldarg.0 - IL_0108: ldc.r8 9.1 - IL_0111: blt IL_01dd - IL_0116: ldarg.0 - IL_0117: ldc.r8 10.1 - IL_0120: bgt.un.s IL_0142 - IL_0122: ldarg.0 - IL_0123: ldc.r8 9.1 - IL_012c: beq.s IL_019d - IL_012e: ldarg.0 - IL_012f: ldc.r8 10.1 - IL_0138: beq IL_01bd - IL_013d: br IL_01ef - IL_0142: ldarg.0 - IL_0143: ldc.r8 11.1 - IL_014c: beq.s IL_01c6 - IL_014e: ldarg.0 - IL_014f: ldc.r8 12.1 - IL_0158: beq.s IL_01a5 - IL_015a: br IL_01ef - IL_015f: ldarg.0 - IL_0160: ldc.r8 4.1 - IL_0169: bge.un.s IL_0179 - IL_016b: ldarg.0 - IL_016c: ldc.r8 2.1 - IL_0175: bge.s IL_01a1 - IL_0177: br.s IL_01ef - IL_0179: ldarg.0 - IL_017a: ldc.r8 5.1 - IL_0183: bge.s IL_01eb - IL_0185: ldarg.0 - IL_0186: ldc.r8 4.1 - IL_018f: beq.s IL_01ca - IL_0191: br.s IL_01ef - IL_0193: ldc.i4.s 19 - IL_0195: stloc.0 - IL_0196: br.s IL_01f1 - IL_0198: ldc.i4.s 18 - IL_019a: stloc.0 - IL_019b: br.s IL_01f1 - IL_019d: ldc.i4.5 - IL_019e: stloc.0 - IL_019f: br.s IL_01f1 - IL_01a1: ldc.i4.1 - IL_01a2: stloc.0 - IL_01a3: br.s IL_01f1 - IL_01a5: ldc.i4.8 - IL_01a6: stloc.0 - IL_01a7: br.s IL_01f1 - IL_01a9: ldc.i4.s 15 - IL_01ab: stloc.0 - IL_01ac: br.s IL_01f1 - IL_01ae: ldc.i4.s 13 - IL_01b0: stloc.0 - IL_01b1: br.s IL_01f1 - IL_01b3: ldc.i4.s 20 - IL_01b5: stloc.0 - IL_01b6: br.s IL_01f1 - IL_01b8: ldc.i4.s 9 - IL_01ba: stloc.0 - IL_01bb: br.s IL_01f1 - IL_01bd: ldc.i4.6 - IL_01be: stloc.0 - IL_01bf: br.s IL_01f1 - IL_01c1: ldc.i4.s 10 - IL_01c3: stloc.0 - IL_01c4: br.s IL_01f1 - IL_01c6: ldc.i4.7 - IL_01c7: stloc.0 - IL_01c8: br.s IL_01f1 - IL_01ca: ldc.i4.2 - IL_01cb: stloc.0 - IL_01cc: br.s IL_01f1 - IL_01ce: ldc.i4.s 11 - IL_01d0: stloc.0 - IL_01d1: br.s IL_01f1 - IL_01d3: ldc.i4.s 16 - IL_01d5: stloc.0 - IL_01d6: br.s IL_01f1 - IL_01d8: ldc.i4.s 12 - IL_01da: stloc.0 - IL_01db: br.s IL_01f1 - IL_01dd: ldc.i4.4 - IL_01de: stloc.0 - IL_01df: br.s IL_01f1 - IL_01e1: ldc.i4.s 17 - IL_01e3: stloc.0 - IL_01e4: br.s IL_01f1 - IL_01e6: ldc.i4.s 14 - IL_01e8: stloc.0 - IL_01e9: br.s IL_01f1 - IL_01eb: ldc.i4.3 - IL_01ec: stloc.0 - IL_01ed: br.s IL_01f1 - IL_01ef: ldc.i4.0 - IL_01f0: stloc.0 - IL_01f1: ldloc.0 - IL_01f2: ret + IL_001c: ldc.r8 29.1 + IL_0025: beq IL_019e + IL_002a: br IL_01da + IL_002f: ldarg.0 + IL_0030: ldc.r8 26.1 + IL_0039: beq IL_0183 + IL_003e: ldarg.0 + IL_003f: ldc.r8 9.1 + IL_0048: beq IL_0188 + IL_004d: ldarg.0 + IL_004e: ldc.r8 2.1 + IL_0057: blt.un IL_01da + IL_005c: ldarg.0 + IL_005d: ldc.r8 4.1 + IL_0066: blt IL_018c + IL_006b: ldarg.0 + IL_006c: ldc.r8 12.1 + IL_0075: beq IL_0190 + IL_007a: ldarg.0 + IL_007b: ldc.r8 21.1 + IL_0084: blt.un.s IL_00b8 + IL_0086: ldarg.0 + IL_0087: ldc.r8 23.1 + IL_0090: blt IL_0194 + IL_0095: ldarg.0 + IL_0096: ldc.r8 25.1 + IL_009f: blt IL_01be + IL_00a4: ldarg.0 + IL_00a5: ldc.r8 25.1 + IL_00ae: beq IL_01cc + IL_00b3: br IL_01da + IL_00b8: ldarg.0 + IL_00b9: ldc.r8 19.1 + IL_00c2: beq IL_0199 + IL_00c7: ldarg.0 + IL_00c8: ldc.r8 13.1 + IL_00d1: blt.un.s IL_0132 + IL_00d3: ldarg.0 + IL_00d4: ldc.r8 15.1 + IL_00dd: blt IL_01a3 + IL_00e2: ldarg.0 + IL_00e3: ldc.r8 15.1 + IL_00ec: beq IL_01ac + IL_00f1: ldarg.0 + IL_00f2: ldc.r8 16.1 + IL_00fb: blt.un IL_01da + IL_0100: ldarg.0 + IL_0101: ldc.r8 18.1 + IL_010a: blt IL_01b9 + IL_010f: ldarg.0 + IL_0110: ldc.r8 18.1 + IL_0119: beq IL_01c3 + IL_011e: ldarg.0 + IL_011f: ldc.r8 20.1 + IL_0128: beq IL_01d1 + IL_012d: br IL_01da + IL_0132: ldarg.0 + IL_0133: ldc.r8 10.1 + IL_013c: beq.s IL_01a8 + IL_013e: ldarg.0 + IL_013f: ldc.r8 11.1 + IL_0148: beq.s IL_01b1 + IL_014a: ldarg.0 + IL_014b: ldc.r8 4.1 + IL_0154: beq.s IL_01b5 + IL_0156: ldarg.0 + IL_0157: ldc.r8 7.1 + IL_0160: blt.un.s IL_0170 + IL_0162: ldarg.0 + IL_0163: ldc.r8 9.1 + IL_016c: blt.s IL_01c8 + IL_016e: br.s IL_01da + IL_0170: ldarg.0 + IL_0171: ldc.r8 5.1 + IL_017a: bge.s IL_01d6 + IL_017c: br.s IL_01da + IL_017e: ldc.i4.s 19 + IL_0180: stloc.0 + IL_0181: br.s IL_01dc + IL_0183: ldc.i4.s 18 + IL_0185: stloc.0 + IL_0186: br.s IL_01dc + IL_0188: ldc.i4.5 + IL_0189: stloc.0 + IL_018a: br.s IL_01dc + IL_018c: ldc.i4.1 + IL_018d: stloc.0 + IL_018e: br.s IL_01dc + IL_0190: ldc.i4.8 + IL_0191: stloc.0 + IL_0192: br.s IL_01dc + IL_0194: ldc.i4.s 15 + IL_0196: stloc.0 + IL_0197: br.s IL_01dc + IL_0199: ldc.i4.s 13 + IL_019b: stloc.0 + IL_019c: br.s IL_01dc + IL_019e: ldc.i4.s 20 + IL_01a0: stloc.0 + IL_01a1: br.s IL_01dc + IL_01a3: ldc.i4.s 9 + IL_01a5: stloc.0 + IL_01a6: br.s IL_01dc + IL_01a8: ldc.i4.6 + IL_01a9: stloc.0 + IL_01aa: br.s IL_01dc + IL_01ac: ldc.i4.s 10 + IL_01ae: stloc.0 + IL_01af: br.s IL_01dc + IL_01b1: ldc.i4.7 + IL_01b2: stloc.0 + IL_01b3: br.s IL_01dc + IL_01b5: ldc.i4.2 + IL_01b6: stloc.0 + IL_01b7: br.s IL_01dc + IL_01b9: ldc.i4.s 11 + IL_01bb: stloc.0 + IL_01bc: br.s IL_01dc + IL_01be: ldc.i4.s 16 + IL_01c0: stloc.0 + IL_01c1: br.s IL_01dc + IL_01c3: ldc.i4.s 12 + IL_01c5: stloc.0 + IL_01c6: br.s IL_01dc + IL_01c8: ldc.i4.4 + IL_01c9: stloc.0 + IL_01ca: br.s IL_01dc + IL_01cc: ldc.i4.s 17 + IL_01ce: stloc.0 + IL_01cf: br.s IL_01dc + IL_01d1: ldc.i4.s 14 + IL_01d3: stloc.0 + IL_01d4: br.s IL_01dc + IL_01d6: ldc.i4.3 + IL_01d7: stloc.0 + IL_01d8: br.s IL_01dc + IL_01da: ldc.i4.0 + IL_01db: stloc.0 + IL_01dc: ldloc.0 + IL_01dd: ret } " ); @@ -10177,162 +10171,156 @@ static int M(float d) expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M", @" { - // Code size 388 (0x184) + // Code size 374 (0x176) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.0 - IL_0001: ldc.r4 13.1 - IL_0006: blt.un IL_00bb - IL_000b: ldarg.0 - IL_000c: ldc.r4 21.1 - IL_0011: blt.un.s IL_0067 + IL_0001: ldc.r4 27.1 + IL_0006: blt.un.s IL_0023 + IL_0008: ldarg.0 + IL_0009: ldc.r4 29.1 + IL_000e: blt IL_0116 IL_0013: ldarg.0 - IL_0014: ldc.r4 27.1 - IL_0019: blt.un.s IL_0036 - IL_001b: ldarg.0 - IL_001c: ldc.r4 29.1 - IL_0021: blt IL_0124 - IL_0026: ldarg.0 - IL_0027: ldc.r4 29.1 - IL_002c: beq IL_0144 - IL_0031: br IL_0180 - IL_0036: ldarg.0 - IL_0037: ldc.r4 23.1 - IL_003c: blt IL_013a - IL_0041: ldarg.0 - IL_0042: ldc.r4 25.1 - IL_0047: blt IL_0164 - IL_004c: ldarg.0 - IL_004d: ldc.r4 25.1 - IL_0052: beq IL_0172 - IL_0057: ldarg.0 - IL_0058: ldc.r4 26.1 - IL_005d: beq IL_0129 - IL_0062: br IL_0180 - IL_0067: ldarg.0 - IL_0068: ldc.r4 16.1 - IL_006d: blt.un.s IL_00a0 - IL_006f: ldarg.0 - IL_0070: ldc.r4 18.1 - IL_0075: blt IL_015f - IL_007a: ldarg.0 - IL_007b: ldc.r4 18.1 - IL_0080: beq IL_0169 - IL_0085: ldarg.0 - IL_0086: ldc.r4 19.1 - IL_008b: beq IL_013f - IL_0090: ldarg.0 - IL_0091: ldc.r4 20.1 - IL_0096: beq IL_0177 - IL_009b: br IL_0180 - IL_00a0: ldarg.0 - IL_00a1: ldc.r4 15.1 - IL_00a6: blt IL_0149 - IL_00ab: ldarg.0 - IL_00ac: ldc.r4 15.1 - IL_00b1: beq IL_0152 - IL_00b6: br IL_0180 - IL_00bb: ldarg.0 - IL_00bc: ldc.r4 7.1 - IL_00c1: blt.un.s IL_0100 - IL_00c3: ldarg.0 - IL_00c4: ldc.r4 9.1 - IL_00c9: blt IL_016e - IL_00ce: ldarg.0 - IL_00cf: ldc.r4 10.1 - IL_00d4: bgt.un.s IL_00eb - IL_00d6: ldarg.0 - IL_00d7: ldc.r4 9.1 - IL_00dc: beq.s IL_012e - IL_00de: ldarg.0 - IL_00df: ldc.r4 10.1 - IL_00e4: beq.s IL_014e - IL_00e6: br IL_0180 - IL_00eb: ldarg.0 - IL_00ec: ldc.r4 11.1 - IL_00f1: beq.s IL_0157 - IL_00f3: ldarg.0 - IL_00f4: ldc.r4 12.1 - IL_00f9: beq.s IL_0136 - IL_00fb: br IL_0180 - IL_0100: ldarg.0 - IL_0101: ldc.r4 4.1 - IL_0106: bge.un.s IL_0112 - IL_0108: ldarg.0 - IL_0109: ldc.r4 2.1 - IL_010e: bge.s IL_0132 - IL_0110: br.s IL_0180 - IL_0112: ldarg.0 - IL_0113: ldc.r4 5.1 - IL_0118: bge.s IL_017c - IL_011a: ldarg.0 - IL_011b: ldc.r4 4.1 - IL_0120: beq.s IL_015b - IL_0122: br.s IL_0180 - IL_0124: ldc.i4.s 19 - IL_0126: stloc.0 - IL_0127: br.s IL_0182 - IL_0129: ldc.i4.s 18 - IL_012b: stloc.0 - IL_012c: br.s IL_0182 - IL_012e: ldc.i4.5 - IL_012f: stloc.0 - IL_0130: br.s IL_0182 - IL_0132: ldc.i4.1 + IL_0014: ldc.r4 29.1 + IL_0019: beq IL_0136 + IL_001e: br IL_0172 + IL_0023: ldarg.0 + IL_0024: ldc.r4 26.1 + IL_0029: beq IL_011b + IL_002e: ldarg.0 + IL_002f: ldc.r4 9.1 + IL_0034: beq IL_0120 + IL_0039: ldarg.0 + IL_003a: ldc.r4 2.1 + IL_003f: blt.un IL_0172 + IL_0044: ldarg.0 + IL_0045: ldc.r4 4.1 + IL_004a: blt IL_0124 + IL_004f: ldarg.0 + IL_0050: ldc.r4 12.1 + IL_0055: beq IL_0128 + IL_005a: ldarg.0 + IL_005b: ldc.r4 21.1 + IL_0060: blt.un.s IL_0088 + IL_0062: ldarg.0 + IL_0063: ldc.r4 23.1 + IL_0068: blt IL_012c + IL_006d: ldarg.0 + IL_006e: ldc.r4 25.1 + IL_0073: blt IL_0156 + IL_0078: ldarg.0 + IL_0079: ldc.r4 25.1 + IL_007e: beq IL_0164 + IL_0083: br IL_0172 + IL_0088: ldarg.0 + IL_0089: ldc.r4 19.1 + IL_008e: beq IL_0131 + IL_0093: ldarg.0 + IL_0094: ldc.r4 13.1 + IL_0099: blt.un.s IL_00e2 + IL_009b: ldarg.0 + IL_009c: ldc.r4 15.1 + IL_00a1: blt IL_013b + IL_00a6: ldarg.0 + IL_00a7: ldc.r4 15.1 + IL_00ac: beq IL_0144 + IL_00b1: ldarg.0 + IL_00b2: ldc.r4 16.1 + IL_00b7: blt.un IL_0172 + IL_00bc: ldarg.0 + IL_00bd: ldc.r4 18.1 + IL_00c2: blt IL_0151 + IL_00c7: ldarg.0 + IL_00c8: ldc.r4 18.1 + IL_00cd: beq IL_015b + IL_00d2: ldarg.0 + IL_00d3: ldc.r4 20.1 + IL_00d8: beq IL_0169 + IL_00dd: br IL_0172 + IL_00e2: ldarg.0 + IL_00e3: ldc.r4 10.1 + IL_00e8: beq.s IL_0140 + IL_00ea: ldarg.0 + IL_00eb: ldc.r4 11.1 + IL_00f0: beq.s IL_0149 + IL_00f2: ldarg.0 + IL_00f3: ldc.r4 4.1 + IL_00f8: beq.s IL_014d + IL_00fa: ldarg.0 + IL_00fb: ldc.r4 7.1 + IL_0100: blt.un.s IL_010c + IL_0102: ldarg.0 + IL_0103: ldc.r4 9.1 + IL_0108: blt.s IL_0160 + IL_010a: br.s IL_0172 + IL_010c: ldarg.0 + IL_010d: ldc.r4 5.1 + IL_0112: bge.s IL_016e + IL_0114: br.s IL_0172 + IL_0116: ldc.i4.s 19 + IL_0118: stloc.0 + IL_0119: br.s IL_0174 + IL_011b: ldc.i4.s 18 + IL_011d: stloc.0 + IL_011e: br.s IL_0174 + IL_0120: ldc.i4.5 + IL_0121: stloc.0 + IL_0122: br.s IL_0174 + IL_0124: ldc.i4.1 + IL_0125: stloc.0 + IL_0126: br.s IL_0174 + IL_0128: ldc.i4.8 + IL_0129: stloc.0 + IL_012a: br.s IL_0174 + IL_012c: ldc.i4.s 15 + IL_012e: stloc.0 + IL_012f: br.s IL_0174 + IL_0131: ldc.i4.s 13 IL_0133: stloc.0 - IL_0134: br.s IL_0182 - IL_0136: ldc.i4.8 - IL_0137: stloc.0 - IL_0138: br.s IL_0182 - IL_013a: ldc.i4.s 15 - IL_013c: stloc.0 - IL_013d: br.s IL_0182 - IL_013f: ldc.i4.s 13 + IL_0134: br.s IL_0174 + IL_0136: ldc.i4.s 20 + IL_0138: stloc.0 + IL_0139: br.s IL_0174 + IL_013b: ldc.i4.s 9 + IL_013d: stloc.0 + IL_013e: br.s IL_0174 + IL_0140: ldc.i4.6 IL_0141: stloc.0 - IL_0142: br.s IL_0182 - IL_0144: ldc.i4.s 20 + IL_0142: br.s IL_0174 + IL_0144: ldc.i4.s 10 IL_0146: stloc.0 - IL_0147: br.s IL_0182 - IL_0149: ldc.i4.s 9 - IL_014b: stloc.0 - IL_014c: br.s IL_0182 - IL_014e: ldc.i4.6 - IL_014f: stloc.0 - IL_0150: br.s IL_0182 - IL_0152: ldc.i4.s 10 - IL_0154: stloc.0 - IL_0155: br.s IL_0182 - IL_0157: ldc.i4.7 + IL_0147: br.s IL_0174 + IL_0149: ldc.i4.7 + IL_014a: stloc.0 + IL_014b: br.s IL_0174 + IL_014d: ldc.i4.2 + IL_014e: stloc.0 + IL_014f: br.s IL_0174 + IL_0151: ldc.i4.s 11 + IL_0153: stloc.0 + IL_0154: br.s IL_0174 + IL_0156: ldc.i4.s 16 IL_0158: stloc.0 - IL_0159: br.s IL_0182 - IL_015b: ldc.i4.2 - IL_015c: stloc.0 - IL_015d: br.s IL_0182 - IL_015f: ldc.i4.s 11 + IL_0159: br.s IL_0174 + IL_015b: ldc.i4.s 12 + IL_015d: stloc.0 + IL_015e: br.s IL_0174 + IL_0160: ldc.i4.4 IL_0161: stloc.0 - IL_0162: br.s IL_0182 - IL_0164: ldc.i4.s 16 + IL_0162: br.s IL_0174 + IL_0164: ldc.i4.s 17 IL_0166: stloc.0 - IL_0167: br.s IL_0182 - IL_0169: ldc.i4.s 12 + IL_0167: br.s IL_0174 + IL_0169: ldc.i4.s 14 IL_016b: stloc.0 - IL_016c: br.s IL_0182 - IL_016e: ldc.i4.4 + IL_016c: br.s IL_0174 + IL_016e: ldc.i4.3 IL_016f: stloc.0 - IL_0170: br.s IL_0182 - IL_0172: ldc.i4.s 17 - IL_0174: stloc.0 - IL_0175: br.s IL_0182 - IL_0177: ldc.i4.s 14 - IL_0179: stloc.0 - IL_017a: br.s IL_0182 - IL_017c: ldc.i4.3 - IL_017d: stloc.0 - IL_017e: br.s IL_0182 - IL_0180: ldc.i4.0 - IL_0181: stloc.0 - IL_0182: ldloc.0 - IL_0183: ret + IL_0170: br.s IL_0174 + IL_0172: ldc.i4.0 + IL_0173: stloc.0 + IL_0174: ldloc.0 + IL_0175: ret } " ); diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 4a9568a43ef6e..9cdccab77612b 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -13716,5 +13716,333 @@ .maxstack 1 }) .Verify(); } + + [Fact] + public void FileTypes_01() + { + var source0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file1.cs"); + + var compilation0 = CreateCompilation(source0.Tree, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(source1.Tree); + + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + } + + [Fact] + public void FileTypes_02() + { + var source0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file1.cs"); + var source2 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(3); + } +}", "file2.cs"); + + var compilation0 = CreateCompilation(source0.Tree, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1.Tree, source2.Tree }); + + var cm1_gen0 = compilation0.GetMember("C.M"); + var cm1_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[0]).GetMember("M"); + var c2_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[1]); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, cm1_gen0, cm1_gen1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + + diff.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + } + + [Fact] + public void FileTypes_03() + { + var source0_gen0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source1_gen1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file2.cs"); + var source0_gen1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(3); + } +}", "file1.cs"); + + var compilation0 = CreateCompilation(source0_gen0.Tree, options: ComSafeDebugDll); + // Because the order of syntax trees has changed here, the original type is considered deleted and the two new types are completely new, unrelated types. + + // https://github.com/dotnet/roslyn/issues/61999 + // we should handle this as a modification of an existing type rather than deletion and insertion of distinct types. + // most likely, we either need to identify file types based on something stable like the SyntaxTree.FilePath, or store a mapping of the ordinals from one generation to the next. + // although "real-world" compilations disallow duplicated file paths, duplicated or empty file paths are very common via direct use of the APIs, so there's not necessarily a single slam-dunk answer here. + var compilation1 = compilation0.WithSource(new[] { source1_gen1.Tree, source0_gen1.Tree }); + + var c1_gen0 = compilation0.GetMember("C"); + var c1_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[0]); + var c2_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[1]); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Delete, c1_gen0, null, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c1_gen1, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + + diff.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + } + + [Fact] + public void FileTypes_04() + { + var source1_gen0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source2_gen0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file2.cs"); + var source2_gen1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(3); + } +}", "file2.cs"); + + var compilation0 = CreateCompilation(new[] { source1_gen0.Tree, source2_gen0.Tree }, options: ComSafeDebugDll); + + var compilation1 = compilation0.WithSource(new[] { source2_gen1.Tree }); + + var c1_gen0 = ((NamedTypeSymbol)compilation0.GetMembers("C")[0]); + var c2_gen0 = ((NamedTypeSymbol)compilation0.GetMembers("C")[1]); + var c2_gen1 = compilation1.GetMember("C"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Delete, c1_gen0, null, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Delete, c2_gen0, null, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + } } } diff --git a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs index 75d93a1dc8865..60da94c4657fb 100644 --- a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs @@ -10773,7 +10773,7 @@ static void Main() } } "; - var verifier = CompileAndVerify(source, expectedOutput: "not found"); + var verifier = CompileAndVerify(source, expectedOutput: "Attr`1[System.String]"); verifier.VerifyDiagnostics(); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index df87ce161d64c..d134238caf131 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -9748,6 +9748,40 @@ public MyAttribute(string name1) { } ); } + [Fact, WorkItem(1556927, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1556927")] + public void ParameterScope_ValueLocalNotInPropertyOrAccessorAttributeNameOf_UnknownAccessor() + { + var source = @" +class C +{ + int Property4 { [My(nameof(value))] unknown => throw null; } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name) { } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics( + // (4,9): error CS0548: 'C.Property4': property or indexer must have at least one accessor + // int Property4 { [My(nameof(value))] unknown => throw null; } + Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "Property4").WithArguments("C.Property4").WithLocation(4, 9), + // (4,41): error CS1014: A get or set accessor expected + // int Property4 { [My(nameof(value))] unknown => throw null; } + Diagnostic(ErrorCode.ERR_GetOrSetExpected, "unknown").WithLocation(4, 41) + ); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType() + .Where(i => i.Identifier.ValueText == "value") + .Where(i => i.Ancestors().Any(a => a.IsKind(SyntaxKind.Attribute))) + .Single(); + + Assert.Null(model.GetSymbolInfo(node).Symbol); + } + [Fact] public void ParameterScope_InParameterAttributeNameOf_Constructor() { @@ -10429,5 +10463,32 @@ public MyAttribute(string name1) { } "); comp.VerifyDiagnostics(); } + + [Fact, WorkItem(1556927, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1556927")] + public void LambdaOutsideMemberModel() + { + var text = @" +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} + +int P +{ + badAccessorName + { + M([My(nameof(P))] env => env); +"; + var comp = CreateCompilation(text); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType() + .Where(i => i.Identifier.ValueText == "P") + .Where(i => i.Ancestors().Any(a => a.IsKind(SyntaxKind.Attribute))) + .Single(); + + Assert.Null(model.GetSymbolInfo(node).Symbol); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs index fcfd622eaf39e..2dd6e1e04efeb 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs @@ -4240,6 +4240,513 @@ void M(object obj) [11]: leaf `_ => 4` ", boundSwitch.ReachabilityDecisionDag.Dump()); } + + [Fact, WorkItem(62241, "https://github.com/dotnet/roslyn/issues/62241")] + public void DisableBalancedSwitchDispatchOptimization_Double() + { + var source = """ +C.M(double.NaN); + +public class C +{ + public static void M(double x) + { + string msg = x switch + { + < -40.0 => "Too low", + >= -40.0 and < 0 => "Low", + >= 0 and < 10.0 => "Acceptable", + >= 10.0 => "High", + double.NaN => "NaN", + }; + System.Console.Write(msg); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" +[0]: t0 < -40 ? [1] : [2] +[1]: leaf `< -40.0 => "Too low"` +[2]: t0 >= -40 ? [3] : [8] +[3]: t0 < 0 ? [4] : [5] +[4]: leaf `>= -40.0 and < 0 => "Low"` +[5]: t0 < 10 ? [6] : [7] +[6]: leaf `>= 0 and < 10.0 => "Acceptable"` +[7]: leaf `>= 10.0 => "High"` +[8]: leaf `double.NaN => "NaN"` +""", boundSwitch.ReachabilityDecisionDag.Dump()); + + verifier.VerifyIL("C.M", """ +{ + // Code size 95 (0x5f) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldarg.0 + IL_0001: ldc.r8 -40 + IL_000a: blt.s IL_0032 + IL_000c: ldarg.0 + IL_000d: ldc.r8 -40 + IL_0016: blt.un.s IL_0052 + IL_0018: ldarg.0 + IL_0019: ldc.r8 0 + IL_0022: blt.s IL_003a + IL_0024: ldarg.0 + IL_0025: ldc.r8 10 + IL_002e: blt.s IL_0042 + IL_0030: br.s IL_004a + IL_0032: ldstr "Too low" + IL_0037: stloc.0 + IL_0038: br.s IL_0058 + IL_003a: ldstr "Low" + IL_003f: stloc.0 + IL_0040: br.s IL_0058 + IL_0042: ldstr "Acceptable" + IL_0047: stloc.0 + IL_0048: br.s IL_0058 + IL_004a: ldstr "High" + IL_004f: stloc.0 + IL_0050: br.s IL_0058 + IL_0052: ldstr "NaN" + IL_0057: stloc.0 + IL_0058: ldloc.0 + IL_0059: call "void System.Console.Write(string)" + IL_005e: ret +} +"""); + } + + [Fact, WorkItem(62241, "https://github.com/dotnet/roslyn/issues/62241")] + public void DisableBalancedSwitchDispatchOptimization_Single() + { + var source = """ +C.M(float.NaN); + +public class C +{ + public static void M(float x) + { + string msg = x switch + { + < -40.0f => "Too low", + >= -40.0f and < 0f => "Low", + >= 0f and < 10.0f => "Acceptable", + >= 10.0f => "High", + float.NaN => "NaN", + }; + System.Console.Write(msg); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" +[0]: t0 < -40 ? [1] : [2] +[1]: leaf `< -40.0f => "Too low"` +[2]: t0 >= -40 ? [3] : [8] +[3]: t0 < 0 ? [4] : [5] +[4]: leaf `>= -40.0f and < 0f => "Low"` +[5]: t0 < 10 ? [6] : [7] +[6]: leaf `>= 0f and < 10.0f => "Acceptable"` +[7]: leaf `>= 10.0f => "High"` +[8]: leaf `float.NaN => "NaN"` +""", boundSwitch.ReachabilityDecisionDag.Dump()); + + verifier.VerifyIL("C.M", """ +{ + // Code size 79 (0x4f) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldarg.0 + IL_0001: ldc.r4 -40 + IL_0006: blt.s IL_0022 + IL_0008: ldarg.0 + IL_0009: ldc.r4 -40 + IL_000e: blt.un.s IL_0042 + IL_0010: ldarg.0 + IL_0011: ldc.r4 0 + IL_0016: blt.s IL_002a + IL_0018: ldarg.0 + IL_0019: ldc.r4 10 + IL_001e: blt.s IL_0032 + IL_0020: br.s IL_003a + IL_0022: ldstr "Too low" + IL_0027: stloc.0 + IL_0028: br.s IL_0048 + IL_002a: ldstr "Low" + IL_002f: stloc.0 + IL_0030: br.s IL_0048 + IL_0032: ldstr "Acceptable" + IL_0037: stloc.0 + IL_0038: br.s IL_0048 + IL_003a: ldstr "High" + IL_003f: stloc.0 + IL_0040: br.s IL_0048 + IL_0042: ldstr "NaN" + IL_0047: stloc.0 + IL_0048: ldloc.0 + IL_0049: call "void System.Console.Write(string)" + IL_004e: ret +} +"""); + } + + [Fact, WorkItem(62241, "https://github.com/dotnet/roslyn/issues/62241")] + public void DisableBalancedSwitchDispatchOptimization_Double_StartingWithHigh() + { + var source = """ +C.M(double.NaN); + +public class C +{ + public static void M(double x) + { + string msg = x switch + { + >= 10.0 => "High", + >= 0 and < 10.0 => "Acceptable", + >= -40.0 and < 0 => "Low", + < -40.0 => "Too low", + double.NaN => "NaN", + }; + System.Console.Write(msg); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" +[0]: t0 >= 10 ? [1] : [2] +[1]: leaf `>= 10.0 => "High"` +[2]: t0 >= 0 ? [3] : [4] +[3]: leaf `>= 0 and < 10.0 => "Acceptable"` +[4]: t0 >= -40 ? [5] : [6] +[5]: leaf `>= -40.0 and < 0 => "Low"` +[6]: t0 < -40 ? [7] : [8] +[7]: leaf `< -40.0 => "Too low"` +[8]: leaf `double.NaN => "NaN"` +""", boundSwitch.ReachabilityDecisionDag.Dump()); + + verifier.VerifyIL("C.M", """ +{ + // Code size 95 (0x5f) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldarg.0 + IL_0001: ldc.r8 10 + IL_000a: bge.s IL_0032 + IL_000c: ldarg.0 + IL_000d: ldc.r8 0 + IL_0016: bge.s IL_003a + IL_0018: ldarg.0 + IL_0019: ldc.r8 -40 + IL_0022: bge.s IL_0042 + IL_0024: ldarg.0 + IL_0025: ldc.r8 -40 + IL_002e: blt.s IL_004a + IL_0030: br.s IL_0052 + IL_0032: ldstr "High" + IL_0037: stloc.0 + IL_0038: br.s IL_0058 + IL_003a: ldstr "Acceptable" + IL_003f: stloc.0 + IL_0040: br.s IL_0058 + IL_0042: ldstr "Low" + IL_0047: stloc.0 + IL_0048: br.s IL_0058 + IL_004a: ldstr "Too low" + IL_004f: stloc.0 + IL_0050: br.s IL_0058 + IL_0052: ldstr "NaN" + IL_0057: stloc.0 + IL_0058: ldloc.0 + IL_0059: call "void System.Console.Write(string)" + IL_005e: ret +} +"""); + } + + [Fact, WorkItem(62241, "https://github.com/dotnet/roslyn/issues/62241")] + public void DisableBalancedSwitchDispatchOptimization_Double_StartingWithNaN() + { + var source = """ +C.M(double.NaN); + +public class C +{ + public static void M(double x) + { + string msg = x switch + { + double.NaN => "NaN", + < -40.0 => "Too low", + >= -40.0 and < 0 => "Low", + >= 0 and < 10.0 => "Acceptable", + >= 10.0 => "High", + }; + System.Console.Write(msg); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" +[0]: t0 == NaN ? [1] : [2] +[1]: leaf `double.NaN => "NaN"` +[2]: t0 < -40 ? [3] : [4] +[3]: leaf `< -40.0 => "Too low"` +[4]: t0 < 0 ? [5] : [6] +[5]: leaf `>= -40.0 and < 0 => "Low"` +[6]: t0 < 10 ? [7] : [8] +[7]: leaf `>= 0 and < 10.0 => "Acceptable"` +[8]: leaf `>= 10.0 => "High"` +""", boundSwitch.ReachabilityDecisionDag.Dump()); + + verifier.VerifyIL("C.M", """ +{ + // Code size 91 (0x5b) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldarg.0 + IL_0001: call "bool double.IsNaN(double)" + IL_0006: brtrue.s IL_002e + IL_0008: ldarg.0 + IL_0009: ldc.r8 -40 + IL_0012: blt.s IL_0036 + IL_0014: ldarg.0 + IL_0015: ldc.r8 0 + IL_001e: blt.s IL_003e + IL_0020: ldarg.0 + IL_0021: ldc.r8 10 + IL_002a: blt.s IL_0046 + IL_002c: br.s IL_004e + IL_002e: ldstr "NaN" + IL_0033: stloc.0 + IL_0034: br.s IL_0054 + IL_0036: ldstr "Too low" + IL_003b: stloc.0 + IL_003c: br.s IL_0054 + IL_003e: ldstr "Low" + IL_0043: stloc.0 + IL_0044: br.s IL_0054 + IL_0046: ldstr "Acceptable" + IL_004b: stloc.0 + IL_004c: br.s IL_0054 + IL_004e: ldstr "High" + IL_0053: stloc.0 + IL_0054: ldloc.0 + IL_0055: call "void System.Console.Write(string)" + IL_005a: ret +} +"""); + } + + [Fact, WorkItem(62241, "https://github.com/dotnet/roslyn/issues/62241")] + public void DisableBalancedSwitchDispatchOptimization_Double_DefaultCase() + { + var source = """ +C.M(double.NaN); + +public class C +{ + public static void M(double x) + { + string msg = x switch + { + < -40.0 => "Too low", + >= -40.0 and < 0 => "Low", + >= 0 and < 10.0 => "Acceptable", + >= 10.0 => "High", + _ => "NaN", + }; + System.Console.Write(msg); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" +[0]: t0 < -40 ? [1] : [2] +[1]: leaf `< -40.0 => "Too low"` +[2]: t0 >= -40 ? [3] : [8] +[3]: t0 < 0 ? [4] : [5] +[4]: leaf `>= -40.0 and < 0 => "Low"` +[5]: t0 < 10 ? [6] : [7] +[6]: leaf `>= 0 and < 10.0 => "Acceptable"` +[7]: leaf `>= 10.0 => "High"` +[8]: leaf `_ => "NaN"` +""", boundSwitch.ReachabilityDecisionDag.Dump()); + + verifier.VerifyIL("C.M", """ +{ + // Code size 95 (0x5f) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldarg.0 + IL_0001: ldc.r8 -40 + IL_000a: blt.s IL_0032 + IL_000c: ldarg.0 + IL_000d: ldc.r8 -40 + IL_0016: blt.un.s IL_0052 + IL_0018: ldarg.0 + IL_0019: ldc.r8 0 + IL_0022: blt.s IL_003a + IL_0024: ldarg.0 + IL_0025: ldc.r8 10 + IL_002e: blt.s IL_0042 + IL_0030: br.s IL_004a + IL_0032: ldstr "Too low" + IL_0037: stloc.0 + IL_0038: br.s IL_0058 + IL_003a: ldstr "Low" + IL_003f: stloc.0 + IL_0040: br.s IL_0058 + IL_0042: ldstr "Acceptable" + IL_0047: stloc.0 + IL_0048: br.s IL_0058 + IL_004a: ldstr "High" + IL_004f: stloc.0 + IL_0050: br.s IL_0058 + IL_0052: ldstr "NaN" + IL_0057: stloc.0 + IL_0058: ldloc.0 + IL_0059: call "void System.Console.Write(string)" + IL_005e: ret +} +"""); + } + + [Fact, WorkItem(62241, "https://github.com/dotnet/roslyn/issues/62241")] + public void DisableBalancedSwitchDispatchOptimization_Double_WhenClause() + { + var source = """ +C.M(double.NaN); + +public class C +{ + public static void M(double x) + { + bool b = true; + string msg = x switch + { + < -40.0 => "Too low", + >= -40.0 and < 0 => "Low", + >= 0 and < 10.0 => "Acceptable", + >= 10.0 => "High", + double.NaN when b => "NaN", + _ => "Other", + }; + System.Console.Write(msg); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" +[0]: t0 < -40 ? [1] : [2] +[1]: leaf `< -40.0 => "Too low"` +[2]: t0 >= -40 ? [3] : [8] +[3]: t0 < 0 ? [4] : [5] +[4]: leaf `>= -40.0 and < 0 => "Low"` +[5]: t0 < 10 ? [6] : [7] +[6]: leaf `>= 0 and < 10.0 => "Acceptable"` +[7]: leaf `>= 10.0 => "High"` +[8]: when (b) ? [10] : [9] +[9]: leaf `_ => "Other"` +[10]: leaf `double.NaN when b => "NaN"` +""", boundSwitch.ReachabilityDecisionDag.Dump()); + + verifier.VerifyIL("C.M", """ +{ + // Code size 110 (0x6e) + .maxstack 2 + .locals init (bool V_0, //b + string V_1, + double V_2) + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldarg.0 + IL_0003: stloc.2 + IL_0004: ldloc.2 + IL_0005: ldc.r8 -40 + IL_000e: blt.s IL_0036 + IL_0010: ldloc.2 + IL_0011: ldc.r8 -40 + IL_001a: blt.un.s IL_0056 + IL_001c: ldloc.2 + IL_001d: ldc.r8 0 + IL_0026: blt.s IL_003e + IL_0028: ldloc.2 + IL_0029: ldc.r8 10 + IL_0032: blt.s IL_0046 + IL_0034: br.s IL_004e + IL_0036: ldstr "Too low" + IL_003b: stloc.1 + IL_003c: br.s IL_0067 + IL_003e: ldstr "Low" + IL_0043: stloc.1 + IL_0044: br.s IL_0067 + IL_0046: ldstr "Acceptable" + IL_004b: stloc.1 + IL_004c: br.s IL_0067 + IL_004e: ldstr "High" + IL_0053: stloc.1 + IL_0054: br.s IL_0067 + IL_0056: ldloc.0 + IL_0057: brfalse.s IL_0061 + IL_0059: ldstr "NaN" + IL_005e: stloc.1 + IL_005f: br.s IL_0067 + IL_0061: ldstr "Other" + IL_0066: stloc.1 + IL_0067: ldloc.1 + IL_0068: call "void System.Console.Write(string)" + IL_006d: ret +} +"""); + } #endif } } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/CSharpCompilationOptionsTests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/CSharpCompilationOptionsTests.cs index 6402db2ac68d2..13a70fbfb5f7e 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/CSharpCompilationOptionsTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/CSharpCompilationOptionsTests.cs @@ -354,7 +354,7 @@ public void ConstructorValidation() } /// - /// If this test fails, please update the + /// If this test fails, please update the /// and methods to /// make sure they are doing the right thing with your new field and then update the baseline /// here. diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs index 2fcaaecdc8f09..2ccef65358122 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs @@ -23,7 +23,7 @@ public void Test1() var mscorlibRef = TestMetadata.Net40.mscorlib; var compilation = CSharpCompilation.Create("Test", references: new MetadataReference[] { mscorlibRef }); var sys = compilation.GlobalNamespace.ChildNamespace("System"); - Conversions c = new BuckStopsHereBinder(compilation).Conversions; + Conversions c = new BuckStopsHereBinder(compilation, associatedSyntaxTree: null).Conversions; var types = new TypeSymbol[] { sys.ChildType("Object"), @@ -311,7 +311,7 @@ class C Assert.True(typeIntArrayWithCustomModifiers.HasCustomModifiers(flagNonDefaultArraySizesOrLowerBounds: false)); - var conv = new BuckStopsHereBinder(compilation).Conversions; + var conv = new BuckStopsHereBinder(compilation, associatedSyntaxTree: null).Conversions; HashSet useSiteDiagnostics = null; // no custom modifiers to custom modifiers diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs index d5e358d3b6939..db91e25e01b09 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs @@ -44,6 +44,8 @@ internal override bool MangleName } } + internal override SyntaxTree AssociatedSyntaxTree => null; + public override ImmutableArray TypeParameters { get diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs new file mode 100644 index 0000000000000..47029fb59b42d --- /dev/null +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -0,0 +1,3365 @@ +// 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.Text; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests; + +public class FileModifierTests : CSharpTestBase +{ + [Fact] + public void LangVersion() + { + var source = """ + file class C { } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (1,12): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // file class C { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(1, 12)); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void Nested_01() + { + var source = """ + class Outer + { + file class C { } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (3,16): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // file class C { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(3, 16), + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + } + + [Fact] + public void Nested_02() + { + var source = """ + file class Outer + { + class C { } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (1,12): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // file class Outer + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Outer").WithArguments("file types").WithLocation(1, 12)); + verify(); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + verify(); + + void verify() + { + var outer = comp.GetMember("Outer"); + Assert.Equal(Accessibility.Internal, outer.DeclaredAccessibility); + Assert.True(((SourceMemberContainerTypeSymbol)outer).IsFile); + + var classC = comp.GetMember("Outer.C"); + Assert.Equal(Accessibility.Private, classC.DeclaredAccessibility); + Assert.False(((SourceMemberContainerTypeSymbol)classC).IsFile); + } + } + + [Fact] + public void Nested_03() + { + var source = """ + file class Outer + { + file class C { } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (1,12): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // file class Outer + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Outer").WithArguments("file types").WithLocation(1, 12), + // (3,16): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // file class C { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(3, 16), + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + } + + [Fact] + public void Nested_04() + { + var source = """ + file class Outer + { + public class C { } + } + + class D + { + void M(Outer.C c) { } // 1 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,10): error CS9051: File type 'Outer.C' cannot be used in a member signature in non-file type 'D'. + // void M(Outer.C c) { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M").WithArguments("Outer.C", "D").WithLocation(8, 10)); + } + + [Fact] + public void Nested_05() + { + var source = """ + file class Outer + { + public class C + { + void M1(Outer outer) { } // ok + void M2(C outer) { } // ok + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void Nested_06() + { + var source = """ + class A1 + { + internal class A2 { } + } + file class B : A1 + { + } + class C : B.A2 // ok: base type is bound as A1.A2 + { + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void SameFileUse_01() + { + var source = """ + using System; + + file class C + { + public static void M() + { + Console.Write(1); + } + } + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("C"); + Assert.Equal("<>F0__C", symbol.MetadataName); + + // The qualified name here is based on `SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes`. + // We don't actually look up based on the file-encoded name of the type. + // This is similar to how generic types work (lookup based on 'C' instead of 'C`1'). + verifier.VerifyIL("C@.M", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: call ""void System.Console.Write(int)"" + IL_0006: ret +}"); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__C", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__C"); + Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void SameFileUse_02() + { + var source = """ + using System; + + file class C + { + public static void M() + { + Console.Write(1); + } + } + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { "", source }, expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("C"); + Assert.Equal("<>F1__C", symbol.MetadataName); + + // The qualified name here is based on `SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes`. + // We don't actually look up based on the file-encoded name of the type. + // This is similar to how generic types work (lookup based on 'C' instead of 'C`1'). + verifier.VerifyIL("C@.M", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: call ""void System.Console.Write(int)"" + IL_0006: ret +}"); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F1__C", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F1__C"); + Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void FileEnum_01() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + class Program + { + static void Main() + { + Console.Write(E.E2); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "E2", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("E"); + Assert.Equal("<>F0__E", symbol.MetadataName); + + verifier.VerifyIL("Program.Main", @" +{ + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: box ""E"" + IL_0006: call ""void System.Console.Write(object)"" + IL_000b: ret +}"); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__E", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__E"); + Assert.Equal(new[] { "value__", "E1", "E2", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void FileEnum_02() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + file class Attr : Attribute + { + public Attr(E e) { } + } + + [Attr(E.E2)] + class Program + { + static void Main() + { + var data = typeof(Program).GetCustomAttributesData(); + Console.Write(data[0].ConstructorArguments[0]); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "(<>F0__E)1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("E"); + Assert.Equal("<>F0__E", symbol.MetadataName); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__E", "<>F0__Attr", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__E"); + Assert.Equal(new[] { "value__", "E1", "E2", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void FileEnum_03() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + class Attr : Attribute + { + public Attr(E e) { } // 1 + } + + [Attr(E.E2)] + class Program + { + static void Main() + { + var data = typeof(Program).GetCustomAttributesData(); + Console.Write(data[0].ConstructorArguments[0]); + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (10,12): error CS9051: File type 'E' cannot be used in a member signature in non-file type 'Attr'. + // public Attr(E e) { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "Attr").WithArguments("E", "Attr").WithLocation(10, 12)); + } + + [Fact] + public void FileEnum_04() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + class Attr : Attribute + { + public Attr(object obj) { } + } + + [Attr(E.E2)] + class Program + { + static void Main() + { + var data = typeof(Program).GetCustomAttributesData(); + Console.Write(data[0].ConstructorArguments[0]); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "(<>F0__E)1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("E"); + Assert.Equal("<>F0__E", symbol.MetadataName); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__E", "Attr", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__E"); + Assert.Equal(new[] { "value__", "E1", "E2", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void OtherFileUse() + { + var source1 = """ + using System; + + file class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var source2 = """ + class Program + { + static void Main() + { + C.M(); // 1 + } + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (5,9): error CS0103: The name 'C' does not exist in the current context + // C.M(); // 1 + Diagnostic(ErrorCode.ERR_NameNotInContext, "C").WithArguments("C").WithLocation(5, 9)); + } + + [Fact] + public void Generic_01() + { + var source = """ + using System; + + C.M(1); + + file class C + { + public static void M(T t) { Console.Write(t); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/MyFile.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C@MyFile.M(T)", @" +{ + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: box ""T"" + IL_0006: call ""void System.Console.Write(object)"" + IL_000b: ret +} +"); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("F0__C`1", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + + var classC = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C`1", classC.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void BadFileNames_01() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/My<>File.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@My__File", c.ToTestDisplayString()); + Assert.Equal("F0__C", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + var expectedSymbol = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C", expectedSymbol.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, expectedSymbol.MemberNames); + } + } + + [Fact] + public void BadFileNames_02() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/MyGeneratedFile.g.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@MyGeneratedFile_g", c.ToTestDisplayString()); + Assert.Equal("F0__C", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + var expectedSymbol = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C", expectedSymbol.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, expectedSymbol.MemberNames); + } + } + + [Fact] + public void DuplicateFileNames_01() + { + var path = "path/to/file.cs"; + var source1 = SyntaxFactory.ParseSyntaxTree(""" + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """, options: TestOptions.RegularPreview, path: path, encoding: Encoding.Default); + var source2 = SyntaxFactory.ParseSyntaxTree(""" + using System; + + file class C + { + public static void M() { Console.Write(2); } + } + """, options: TestOptions.RegularPreview, path: path, encoding: Encoding.Default); + + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + // note that VerifyIL doesn't work in this specific scenario because the files have the same name. + + void symbolValidator(ModuleSymbol module) + { + Assert.NotNull(module.GlobalNamespace.GetMember("F0__C")); + Assert.NotNull(module.GlobalNamespace.GetMember("F1__C")); + } + } + + // Data based on Lexer.ScanIdentifier_FastPath, excluding '/', '\', and ':' because those are path separators. + [Theory] + [InlineData('&')] + [InlineData('\0')] + [InlineData(' ')] + [InlineData('\r')] + [InlineData('\n')] + [InlineData('\t')] + [InlineData('!')] + [InlineData('%')] + [InlineData('(')] + [InlineData(')')] + [InlineData('*')] + [InlineData('+')] + [InlineData(',')] + [InlineData('-')] + [InlineData('.')] + [InlineData(';')] + [InlineData('<')] + [InlineData('=')] + [InlineData('>')] + [InlineData('?')] + [InlineData('[')] + [InlineData(']')] + [InlineData('^')] + [InlineData('{')] + [InlineData('|')] + [InlineData('}')] + [InlineData('~')] + [InlineData('"')] + [InlineData('\'')] + [InlineData('`')] + public void BadFileNames_03(char badChar) + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: $"path/to/My{badChar}File.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@My_File", c.ToTestDisplayString()); + Assert.Equal("F0__C", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + var expectedSymbol = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C", expectedSymbol.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, expectedSymbol.MemberNames); + } + } + + [Fact] + public void Pdb_01() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var expectedMetadataName = "F0__C"; + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/My+File.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: validateSymbols); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@My_File", c.ToTestDisplayString()); + Assert.Equal(expectedMetadataName, c.MetadataName); + + void validateSymbols(ModuleSymbol module) + { + var type = module.GlobalNamespace.GetMember(expectedMetadataName); + Assert.NotNull(type); + Assert.Equal(new[] { "M", ".ctor" }, type.MemberNames); + } + } + + [Theory] + [InlineData("file", "file", "<>F0__C", "<>F1__C")] + [InlineData("file", "", "<>F0__C", "C")] + [InlineData("", "file", "C", "<>F1__C")] + public void Duplication_01(string firstFileModifier, string secondFileModifier, string firstMetadataName, string secondMetadataName) + { + // A file type is allowed to have the same name as a non-file type from a different file. + // When both a file type and non-file type with the same name are in scope, the file type is preferred, since it's "more local". + var source1 = $$""" + using System; + + {{firstFileModifier}} class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var source2 = $$""" + using System; + + {{secondFileModifier}} class C + { + public static void M() + { + Console.Write(2); + } + } + """; + + var main = """ + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source1 + main, source2 }, expectedOutput: "1"); + var comp = (CSharpCompilation)verifier.Compilation; + var cs = comp.GetMembers("C"); + var tree = comp.SyntaxTrees[0]; + var expectedSymbol = cs[0]; + Assert.Equal(firstMetadataName, expectedSymbol.MetadataName); + verify(); + + verifier = CompileAndVerify(new[] { source1, source2 + main }, expectedOutput: "2"); + comp = (CSharpCompilation)verifier.Compilation; + cs = comp.GetMembers("C"); + tree = comp.SyntaxTrees[1]; + expectedSymbol = cs[1]; + Assert.Equal(secondMetadataName, expectedSymbol.MetadataName); + verify(); + + void verify() + { + verifier.VerifyDiagnostics(); + Assert.Equal(2, cs.Length); + Assert.Equal(comp.SyntaxTrees[0], cs[0].DeclaringSyntaxReferences.Single().SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], cs[1].DeclaringSyntaxReferences.Single().SyntaxTree); + + var model = comp.GetSemanticModel(tree, ignoreAccessibility: true); + var cReference = tree.GetRoot().DescendantNodes().OfType().Last().Expression; + var info = model.GetTypeInfo(cReference); + Assert.Equal(expectedSymbol.GetPublicSymbol(), info.Type); + } + } + + [Fact] + public void Duplication_02() + { + // As a sanity check, demonstrate that non-file classes with the same name across different files are disallowed. + var source1 = """ + using System; + + class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var source2 = """ + using System; + + class C + { + public static void M() + { + Console.Write(2); + } + } + """; + + var main = """ + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var comp = CreateCompilation(new[] { source1 + main, source2 }); + verify(); + + comp = CreateCompilation(new[] { source1, source2 + main }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (3,7): error CS0101: The namespace '' already contains a definition for 'C' + // class C + Diagnostic(ErrorCode.ERR_DuplicateNameInNS, "C").WithArguments("C", "").WithLocation(3, 7), + // (5,24): error CS0111: Type 'C' already defines a member called 'M' with the same parameter types + // public static void M() + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "M").WithArguments("M", "C").WithLocation(5, 24), + // (14,11): error CS0121: The call is ambiguous between the following methods or properties: 'C.M()' and 'C.M()' + // C.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M()", "C.M()").WithLocation(14, 11)); + + var cs = comp.GetMember("C"); + var syntaxReferences = cs.DeclaringSyntaxReferences; + Assert.Equal(2, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[1].SyntaxTree); + } + } + + [Fact] + public void Duplication_03() + { + var source1 = """ + using System; + + partial class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var source2 = """ + partial class C + { + } + """; + + var main = """ + using System; + + file class C + { + public static void M() + { + Console.Write(2); + } + } + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source1, source2, main }, expectedOutput: "2"); + var comp = (CSharpCompilation)verifier.Compilation; + comp.VerifyDiagnostics(); + + var cs = comp.GetMembers("C"); + Assert.Equal(2, cs.Length); + + var c0 = cs[0]; + Assert.True(c0 is SourceMemberContainerTypeSymbol { IsFile: false }); + + var syntaxReferences = c0.DeclaringSyntaxReferences; + Assert.Equal(2, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[1].SyntaxTree); + + var c1 = cs[1]; + Assert.True(c1 is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.Equal(comp.SyntaxTrees[2], c1.DeclaringSyntaxReferences.Single().SyntaxTree); + + var tree = comp.SyntaxTrees[2]; + var model = comp.GetSemanticModel(tree, ignoreAccessibility: true); + var cReference = tree.GetRoot().DescendantNodes().OfType().Last().Expression; + var info = model.GetTypeInfo(cReference); + Assert.Equal(c1.GetPublicSymbol(), info.Type); + } + + [Fact] + public void Duplication_04() + { + var source1 = """ + using System; + + class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var main = """ + using System; + + file partial class C + { + public static void M() + { + Console.Write(Number); + } + } + + file partial class C + { + private static int Number => 2; + } + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source1, main }, expectedOutput: "2"); + var comp = (CSharpCompilation)verifier.Compilation; + comp.VerifyDiagnostics(); + + var cs = comp.GetMembers("C"); + Assert.Equal(2, cs.Length); + + var c0 = cs[0]; + Assert.True(c0 is SourceMemberContainerTypeSymbol { IsFile: false }); + Assert.Equal(comp.SyntaxTrees[0], c0.DeclaringSyntaxReferences.Single().SyntaxTree); + + var c1 = cs[1]; + Assert.True(c1 is SourceMemberContainerTypeSymbol { IsFile: true }); + + var syntaxReferences = c1.DeclaringSyntaxReferences; + Assert.Equal(2, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[1].SyntaxTree); + + var tree = comp.SyntaxTrees[1]; + var model = comp.GetSemanticModel(tree, ignoreAccessibility: true); + var cReference = tree.GetRoot().DescendantNodes().OfType().Last().Expression; + var info = model.GetTypeInfo(cReference); + Assert.Equal(c1.GetPublicSymbol(), info.Type); + } + + [Theory] + [CombinatorialData] + public void Duplication_05(bool firstClassIsFile) + { + var source1 = $$""" + using System; + + {{(firstClassIsFile ? "file " : "")}}partial class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var main = """ + using System; + + file partial class C + { + public static void M() + { + Console.Write(2); + } + } + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source1, main }, expectedOutput: "2"); + var comp = (CSharpCompilation)verifier.Compilation; + comp.VerifyDiagnostics(); + + var cs = comp.GetMembers("C"); + Assert.Equal(2, cs.Length); + + var c0 = cs[0]; + Assert.Equal(firstClassIsFile, ((SourceMemberContainerTypeSymbol)c0).IsFile); + Assert.Equal(comp.SyntaxTrees[0], c0.DeclaringSyntaxReferences.Single().SyntaxTree); + + var c1 = cs[1]; + Assert.True(c1 is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.Equal(comp.SyntaxTrees[1], c1.DeclaringSyntaxReferences.Single().SyntaxTree); + + var tree = comp.SyntaxTrees[1]; + var model = comp.GetSemanticModel(tree, ignoreAccessibility: true); + var cReference = tree.GetRoot().DescendantNodes().OfType().Last().Expression; + var info = model.GetTypeInfo(cReference); + Assert.Equal(c1.GetPublicSymbol(), info.Type); + } + + [Fact] + public void Duplication_06() + { + var source1 = """ + using System; + + partial class C + { + public static void M() + { + Console.Write(Number); + } + } + """; + + var source2 = """ + using System; + + partial class C + { + private static int Number => 1; + } + + file class C + { + public static void M() + { + Console.Write(2); + } + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + // https://github.com/dotnet/roslyn/issues/62333: should this diagnostic be more specific? + // the issue more precisely is that a definition for 'C' already exists in the current file--not that it's already in this namespace. + comp.VerifyDiagnostics( + // (8,12): error CS0101: The namespace '' already contains a definition for 'C' + // file class C + Diagnostic(ErrorCode.ERR_DuplicateNameInNS, "C").WithArguments("C", "").WithLocation(8, 12)); + + var cs = comp.GetMembers("C"); + Assert.Equal(2, cs.Length); + + var c0 = cs[0]; + Assert.True(c0 is SourceMemberContainerTypeSymbol { IsFile: false }); + var syntaxReferences = c0.DeclaringSyntaxReferences; + Assert.Equal(2, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[1].SyntaxTree); + + var c1 = cs[1]; + Assert.True(c1 is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.Equal(comp.SyntaxTrees[1], c1.DeclaringSyntaxReferences.Single().SyntaxTree); + + + comp = CreateCompilation(new[] { source2, source1 }); + comp.VerifyDiagnostics( + // (5,24): error CS0111: Type 'C' already defines a member called 'M' with the same parameter types + // public static void M() + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "M").WithArguments("M", "C").WithLocation(5, 24), + // (8,12): error CS0260: Missing partial modifier on declaration of type 'C'; another partial declaration of this type exists + // file class C + Diagnostic(ErrorCode.ERR_MissingPartial, "C").WithArguments("C").WithLocation(8, 12)); + + var c = comp.GetMember("C"); + Assert.True(c is SourceMemberContainerTypeSymbol { IsFile: true }); + syntaxReferences = c.DeclaringSyntaxReferences; + Assert.Equal(3, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[1].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[2].SyntaxTree); + } + + [Fact] + public void Duplication_07() + { + var source1 = """ + using System; + + file partial class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var source2 = """ + using System; + + file partial class C + { + public static void M() + { + Console.Write(Number); + } + } + + file class C + { + private static int Number => 2; + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (11,12): error CS0260: Missing partial modifier on declaration of type 'C'; another partial declaration of this type exists + // file class C + Diagnostic(ErrorCode.ERR_MissingPartial, "C").WithArguments("C").WithLocation(11, 12)); + + var cs = comp.GetMembers("C"); + Assert.Equal(2, cs.Length); + + var c0 = cs[0]; + Assert.True(c0 is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.Equal(comp.SyntaxTrees[0], c0.DeclaringSyntaxReferences.Single().SyntaxTree); + + var c1 = cs[1]; + Assert.True(c1 is SourceMemberContainerTypeSymbol { IsFile: true }); + var syntaxReferences = c1.DeclaringSyntaxReferences; + Assert.Equal(2, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], syntaxReferences[1].SyntaxTree); + + + comp = CreateCompilation(new[] { source2, source1 }); + comp.VerifyDiagnostics( + // (11,12): error CS0260: Missing partial modifier on declaration of type 'C'; another partial declaration of this type exists + // file class C + Diagnostic(ErrorCode.ERR_MissingPartial, "C").WithArguments("C").WithLocation(11, 12)); + + cs = comp.GetMembers("C"); + Assert.Equal(2, cs.Length); + + c0 = cs[0]; + Assert.True(c0 is SourceMemberContainerTypeSymbol { IsFile: true }); + syntaxReferences = c0.DeclaringSyntaxReferences; + Assert.Equal(2, syntaxReferences.Length); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[0].SyntaxTree); + Assert.Equal(comp.SyntaxTrees[0], syntaxReferences[1].SyntaxTree); + + c1 = cs[1]; + Assert.True(c1 is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.Equal(comp.SyntaxTrees[1], c1.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + [Fact] + public void Duplication_08() + { + var source1 = """ + partial class Outer + { + file class C + { + public static void M() { } + } + } + """; + + var source2 = """ + partial class Outer + { + file class C + { + public static void M() { } + } + } + """; + + var source3 = """ + partial class Outer + { + public class C + { + public static void M() { } + } + } + """; + + var compilation = CreateCompilation(new[] { source1, source2, source3 }); + compilation.VerifyDiagnostics( + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16), + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + + var classOuter = compilation.GetMember("Outer"); + var cs = classOuter.GetMembers("C"); + Assert.Equal(3, cs.Length); + Assert.True(cs[0] is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.True(cs[1] is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.True(cs[2] is SourceMemberContainerTypeSymbol { IsFile: false }); + } + + [Fact] + public void Duplication_09() + { + var source1 = """ + namespace NS + { + file class C + { + public static void M() { } + } + } + """; + + var source2 = """ + namespace NS + { + file class C + { + public static void M() { } + } + } + """; + + var source3 = """ + namespace NS + { + public class C + { + public static void M() { } + } + } + """; + + var compilation = CreateCompilation(new[] { source1, source2, source3 }); + compilation.VerifyDiagnostics(); + + var namespaceNS = compilation.GetMember("NS"); + var cs = namespaceNS.GetMembers("C"); + Assert.Equal(3, cs.Length); + Assert.True(cs[0] is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.True(cs[1] is SourceMemberContainerTypeSymbol { IsFile: true }); + Assert.True(cs[2] is SourceMemberContainerTypeSymbol { IsFile: false }); + } + + [Theory] + [InlineData("file", "file")] + [InlineData("file", "")] + [InlineData("", "file")] + public void Duplication_10(string firstFileModifier, string secondFileModifier) + { + var source1 = $$""" + using System; + + partial class Program + { + {{firstFileModifier}} class C + { + public static void M() + { + Console.Write(1); + } + } + } + """; + + var source2 = $$""" + using System; + + partial class Program + { + {{secondFileModifier}} class C + { + public static void M() + { + Console.Write(2); + } + } + } + """; + + var main = """ + partial class Program + { + static void Main() + { + Program.C.M(); + } + } + """; + + var comp = CreateCompilation(new[] { source1 + main, source2 }); + var cs = comp.GetMembers("Program.C"); + var tree = comp.SyntaxTrees[0]; + var expectedSymbol = cs[0]; + verify(); + + comp = CreateCompilation(new[] { source1, source2 + main }); + cs = comp.GetMembers("Program.C"); + tree = comp.SyntaxTrees[1]; + expectedSymbol = cs[1]; + verify(); + + void verify() + { + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.ERR_FileTypeNested).Verify(); + Assert.Equal(2, cs.Length); + Assert.Equal(comp.SyntaxTrees[0], cs[0].DeclaringSyntaxReferences.Single().SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], cs[1].DeclaringSyntaxReferences.Single().SyntaxTree); + + var model = comp.GetSemanticModel(tree, ignoreAccessibility: true); + var cReference = tree.GetRoot().DescendantNodes().OfType().Last(); + var info = model.GetTypeInfo(cReference); + Assert.Equal(expectedSymbol.GetPublicSymbol(), info.Type); + } + } + + [Theory] + [InlineData("file", "file")] + [InlineData("file", "")] + [InlineData("", "file")] + public void Duplication_11(string firstFileModifier, string secondFileModifier) + { + var source1 = $$""" + using System; + + {{firstFileModifier}} partial class Outer + { + internal class C + { + public static void M() + { + Console.Write(1); + } + } + } + """; + + var source2 = $$""" + using System; + + {{secondFileModifier}} partial class Outer + { + internal class C + { + public static void M() + { + Console.Write(2); + } + } + } + """; + + var main = """ + class Program + { + static void Main() + { + Outer.C.M(); + } + } + """; + + var comp = CreateCompilation(new[] { source1 + main, source2 }, options: TestOptions.DebugExe); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.ERR_FileTypeNested).Verify(); + var outers = comp.GetMembers("Outer"); + var cs = outers.Select(o => ((NamedTypeSymbol)o).GetMember("C")).ToArray(); + var tree = comp.SyntaxTrees[0]; + var expectedSymbol = cs[0]; + verify(); + + comp = CreateCompilation(new[] { source1, source2 + main }, options: TestOptions.DebugExe); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.ERR_FileTypeNested).Verify(); + outers = comp.GetMembers("Outer"); + cs = outers.Select(o => ((NamedTypeSymbol)o).GetMember("C")).ToArray(); + tree = comp.SyntaxTrees[1]; + expectedSymbol = cs[1]; + verify(); + + void verify() + { + Assert.Equal(2, cs.Length); + Assert.Equal(comp.SyntaxTrees[0], cs[0].DeclaringSyntaxReferences.Single().SyntaxTree); + Assert.Equal(comp.SyntaxTrees[1], cs[1].DeclaringSyntaxReferences.Single().SyntaxTree); + + var model = comp.GetSemanticModel(tree, ignoreAccessibility: true); + var cReference = tree.GetRoot().DescendantNodes().OfType().Last(); + var info = model.GetTypeInfo(cReference); + Assert.Equal(expectedSymbol.GetPublicSymbol(), info.Type); + } + } + + [Fact] + public void SignatureUsage_01() + { + var source = """ + file class C + { + } + + class D + { + public void M1(C c) { } // 1 + private void M2(C c) { } // 2 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,17): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public void M1(C c) { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M1").WithArguments("C", "D").WithLocation(7, 17), + // (8,18): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // private void M2(C c) { } // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M2").WithArguments("C", "D").WithLocation(8, 18)); + } + + [Fact] + public void SignatureUsage_02() + { + var source = """ + file class C + { + } + + class D + { + public C M1() => new C(); // 1 + private C M2() => new C(); // 2 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,14): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public C M1() => new C(); // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M1").WithArguments("C", "D").WithLocation(7, 14), + // (8,15): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // private C M2() => new C(); // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M2").WithArguments("C", "D").WithLocation(8, 15)); + } + + [Fact] + public void SignatureUsage_03() + { + var source = """ + file class C + { + } + file delegate void D(); + + public class E + { + C field; // 1 + C property { get; set; } // 2 + object this[C c] { get => c; set { } } // 3 + event D @event; // 4 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,7): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'E'. + // C field; // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "field").WithArguments("C", "E").WithLocation(8, 7), + // (8,7): warning CS0169: The field 'E.field' is never used + // C field; // 1 + Diagnostic(ErrorCode.WRN_UnreferencedField, "field").WithArguments("E.field").WithLocation(8, 7), + // (9,7): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'E'. + // C property { get; set; } // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "property").WithArguments("C", "E").WithLocation(9, 7), + // (10,12): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'E'. + // object this[C c] { get => c; set { } } // 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "this").WithArguments("C", "E").WithLocation(10, 12), + // (11,13): error CS9051: File type 'D' cannot be used in a member signature in non-file type 'E'. + // event D @event; // 4 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "@event").WithArguments("D", "E").WithLocation(11, 13), + // (11,13): warning CS0067: The event 'E.event' is never used + // event D @event; // 4 + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "@event").WithArguments("E.event").WithLocation(11, 13)); + } + + [Fact] + public void SignatureUsage_04() + { + var source = """ + file class C + { + public class Inner { } + public delegate void InnerDelegate(); + } + + public class E + { + C.Inner field; // 1 + C.Inner property { get; set; } // 2 + object this[C.Inner inner] { get => inner; set { } } // 3 + event C.InnerDelegate @event; // 4 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (9,13): error CS9051: File type 'C.Inner' cannot be used in a member signature in non-file type 'E'. + // C.Inner field; // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "field").WithArguments("C.Inner", "E").WithLocation(9, 13), + // (9,13): warning CS0169: The field 'E.field' is never used + // C.Inner field; // 1 + Diagnostic(ErrorCode.WRN_UnreferencedField, "field").WithArguments("E.field").WithLocation(9, 13), + // (10,13): error CS9051: File type 'C.Inner' cannot be used in a member signature in non-file type 'E'. + // C.Inner property { get; set; } // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "property").WithArguments("C.Inner", "E").WithLocation(10, 13), + // (11,12): error CS9051: File type 'C.Inner' cannot be used in a member signature in non-file type 'E'. + // object this[C.Inner inner] { get => inner; set { } } // 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "this").WithArguments("C.Inner", "E").WithLocation(11, 12), + // (12,27): error CS9051: File type 'C.InnerDelegate' cannot be used in a member signature in non-file type 'E'. + // event C.InnerDelegate @event; // 4 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "@event").WithArguments("C.InnerDelegate", "E").WithLocation(12, 27), + // (12,27): warning CS0067: The event 'E.event' is never used + // event C.InnerDelegate @event; // 4 + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "@event").WithArguments("E.event").WithLocation(12, 27)); + } + + [Fact] + public void SignatureUsage_05() + { + var source = """ + #pragma warning disable 67, 169 // unused event, field + + file class C + { + public class Inner { } + public delegate void InnerDelegate(); + } + + file class D + { + public class Inner + { + C.Inner field; + C.Inner property { get; set; } + object this[C.Inner inner] { get => inner; set { } } + event C.InnerDelegate @event; + } + } + + class E + { + public class Inner + { + C.Inner field; // 1 + C.Inner property { get; set; } // 2 + object this[C.Inner inner] { get => inner; set { } } // 3 + event C.InnerDelegate @event; // 4 + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (24,17): error CS9051: File type 'C.Inner' cannot be used in a member signature in non-file type 'E.Inner'. + // C.Inner field; // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "field").WithArguments("C.Inner", "E.Inner").WithLocation(24, 17), + // (25,17): error CS9051: File type 'C.Inner' cannot be used in a member signature in non-file type 'E.Inner'. + // C.Inner property { get; set; } // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "property").WithArguments("C.Inner", "E.Inner").WithLocation(25, 17), + // (26,16): error CS9051: File type 'C.Inner' cannot be used in a member signature in non-file type 'E.Inner'. + // object this[C.Inner inner] { get => inner; set { } } // 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "this").WithArguments("C.Inner", "E.Inner").WithLocation(26, 16), + // (27,31): error CS9051: File type 'C.InnerDelegate' cannot be used in a member signature in non-file type 'E.Inner'. + // event C.InnerDelegate @event; // 4 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "@event").WithArguments("C.InnerDelegate", "E.Inner").WithLocation(27, 31)); + } + + [Fact] + public void SignatureUsage_06() + { + var source = """ + file class C + { + } + + delegate void Del1(C c); // 1 + delegate C Del2(); // 2 + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,15): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'Del1'. + // delegate void Del1(C c); // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "Del1").WithArguments("C", "Del1").WithLocation(5, 15), + // (6,12): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'Del2'. + // delegate C Del2(); // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "Del2").WithArguments("C", "Del2").WithLocation(6, 12)); + } + + [Fact] + public void SignatureUsage_07() + { + var source = """ + file class C + { + } + + class D + { + public static D operator +(D d, C c) => d; // 1 + public static C operator -(D d1, D d2) => new C(); // 2 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,30): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public static D operator +(D d, C c) => d; // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("C", "D").WithLocation(7, 30), + // (8,30): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public static C operator -(D d1, D d2) => new C(); // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "-").WithArguments("C", "D").WithLocation(8, 30)); + } + + [Fact] + public void SignatureUsage_08() + { + var source = """ + file class C + { + } + + class D + { + public D(C c) { } // 1 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,12): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public D(C c) { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "D").WithArguments("C", "D").WithLocation(7, 12)); + } + + [Fact] + public void SignatureUsage_09() + { + var source = """ + file class C + { + } + + class D + { + public C M(C c1, C c2) => c1; // 1, 2, 3 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,14): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public C M(C c1, C c2) => c1; // 1, 2, 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M").WithArguments("C", "D").WithLocation(7, 14), + // (7,14): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public C M(C c1, C c2) => c1; // 1, 2, 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M").WithArguments("C", "D").WithLocation(7, 14), + // (7,14): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'D'. + // public C M(C c1, C c2) => c1; // 1, 2, 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M").WithArguments("C", "D").WithLocation(7, 14)); + } + + [Fact] + public void AccessModifiers_01() + { + var source = """ + public file class C { } // 1 + file internal class D { } // 2 + private file class E { } // 3, 4 + file class F { } // ok + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (1,19): error CS9052: File type 'C' cannot use accessibility modifiers. + // public file class C { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeNoExplicitAccessibility, "C").WithArguments("C").WithLocation(1, 19), + // (2,21): error CS9052: File type 'D' cannot use accessibility modifiers. + // file internal class D { } // 2 + Diagnostic(ErrorCode.ERR_FileTypeNoExplicitAccessibility, "D").WithArguments("D").WithLocation(2, 21), + // (3,20): error CS9052: File type 'E' cannot use accessibility modifiers. + // private file class E { } // 3, 4 + Diagnostic(ErrorCode.ERR_FileTypeNoExplicitAccessibility, "E").WithArguments("E").WithLocation(3, 20), + // (3,20): error CS1527: Elements defined in a namespace cannot be explicitly declared as private, protected, protected internal, or private protected + // private file class E { } // 3, 4 + Diagnostic(ErrorCode.ERR_NoNamespacePrivate, "E").WithLocation(3, 20)); + } + + [Fact] + public void DuplicateModifiers_01() + { + var source = """ + file file class C { } // 1 + file readonly file struct D { } // 2 + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (1,6): error CS1004: Duplicate 'file' modifier + // file file class C { } // 1 + Diagnostic(ErrorCode.ERR_DuplicateModifier, "file").WithArguments("file").WithLocation(1, 6), + // (2,15): error CS1004: Duplicate 'file' modifier + // file readonly file struct D { } // 2 + Diagnostic(ErrorCode.ERR_DuplicateModifier, "file").WithArguments("file").WithLocation(2, 15)); + } + + [Fact] + public void BaseClause_01() + { + var source = """ + file class Base { } + class Derived1 : Base { } // 1 + public class Derived2 : Base { } // 2, 3 + file class Derived3 : Base { } // ok + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (2,7): error CS9053: File type 'Base' cannot be used as a base type of non-file type 'Derived1'. + // class Derived1 : Base { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeBase, "Derived1").WithArguments("Base", "Derived1").WithLocation(2, 7), + // (3,14): error CS0060: Inconsistent accessibility: base class 'Base' is less accessible than class 'Derived2' + // public class Derived2 : Base { } // 2, 3 + Diagnostic(ErrorCode.ERR_BadVisBaseClass, "Derived2").WithArguments("Derived2", "Base").WithLocation(3, 14), + // (3,14): error CS9053: File type 'Base' cannot be used as a base type of non-file type 'Derived2'. + // public class Derived2 : Base { } // 2, 3 + Diagnostic(ErrorCode.ERR_FileTypeBase, "Derived2").WithArguments("Base", "Derived2").WithLocation(3, 14)); + } + + [Fact] + public void BaseClause_02() + { + var source = """ + file interface Interface { } + + class Derived1 : Interface { } // ok + file class Derived2 : Interface { } // ok + + interface Derived3 : Interface { } // 1 + file interface Derived4 : Interface { } // ok + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (6,11): error CS9053: File type 'Interface' cannot be used as a base type of non-file type 'Derived3'. + // interface Derived3 : Interface { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeBase, "Derived3").WithArguments("Interface", "Derived3").WithLocation(6, 11)); + } + + [Fact] + public void BaseClause_03() + { + var source1 = """ + using System; + class Base + { + public static void M0() + { + Console.Write(1); + } + } + """; + var source2 = """ + using System; + + file class Base + { + public static void M0() + { + Console.Write(2); + } + } + file class Program : Base + { + static void Main() + { + M0(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "2"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; + + var tree = comp.SyntaxTrees[1]; + var model = comp.GetSemanticModel(tree); + + var fileClassBase = (NamedTypeSymbol)comp.GetMembers("Base")[1]; + var expectedSymbol = fileClassBase.GetMember("M0"); + + var node = tree.GetRoot().DescendantNodes().OfType().Last(); + var symbolInfo = model.GetSymbolInfo(node.Expression); + Assert.Equal(expectedSymbol.GetPublicSymbol(), symbolInfo.Symbol); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + } + + [Fact] + public void BaseClause_04() + { + var source1 = """ + using System; + class Base + { + public static void M0() + { + Console.Write(1); + } + } + """; + var source2 = """ + file class Program : Base + { + static void Main() + { + M0(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; + + var tree = comp.SyntaxTrees[1]; + var model = comp.GetSemanticModel(tree); + + var expectedSymbol = comp.GetMember("Base.M0"); + + var node = tree.GetRoot().DescendantNodes().OfType().Last(); + var symbolInfo = model.GetSymbolInfo(node.Expression); + Assert.Equal(expectedSymbol.GetPublicSymbol(), symbolInfo.Symbol); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + } + + [Fact] + public void BaseClause_05() + { + var source = """ + interface I2 { } + file interface I1 { } + partial interface Derived : I1 { } // 1 + partial interface Derived : I2 { } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,19): error CS9053: File type 'I1' cannot be used as a base type of non-file type 'Derived'. + // partial interface Derived : I1 { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeBase, "Derived").WithArguments("I1", "Derived").WithLocation(3, 19)); + } + + [Fact] + public void InterfaceImplementation_01() + { + var source = """ + file interface I + { + void F(); + } + class C : I + { + public void F() { } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void InterfaceImplementation_02() + { + var source = """ + file interface I + { + void F(I i); + } + class C : I + { + public void F(I i) { } // 1 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,17): error CS9051: File type 'I' cannot be used in a member signature in non-file type 'C'. + // public void F(I i) { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "F").WithArguments("I", "C").WithLocation(7, 17)); + } + + [Fact] + public void InterfaceImplementation_03() + { + var source = """ + file interface I + { + void F(I i); + } + class C : I + { + void I.F(I i) { } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,12): error CS9051: File type 'I' cannot be used in a member signature in non-file type 'C'. + // void I.F(I i) { } + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "F").WithArguments("I", "C").WithLocation(7, 12)); + } + + [Fact] + public void InterfaceImplementation_04() + { + var source1 = """ + file interface I + { + void F(); + } + partial class C : I + { + } + """; + + var source2 = """ + partial class C + { + public void F() { } + } + """; + + // This is similar to how a base class may not have access to an interface (by being from another assembly, etc.), + // but a derived class might add that interface to its list, and a base member implicitly implements an interface member. + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void InterfaceImplementation_05() + { + var source1 = """ + file interface I + { + void F(); + } + partial class C : I // 1 + { + } + """; + + var source2 = """ + partial class C + { + void I.F() { } // 2, 3 + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (3,10): error CS0246: The type or namespace name 'I' could not be found (are you missing a using directive or an assembly reference?) + // void I.F() { } // 2, 3 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "I").WithArguments("I").WithLocation(3, 10), + // (3,10): error CS0538: 'I' in explicit interface declaration is not an interface + // void I.F() { } // 2, 3 + Diagnostic(ErrorCode.ERR_ExplicitInterfaceImplementationNotInterface, "I").WithArguments("I").WithLocation(3, 10), + // (5,19): error CS0535: 'C' does not implement interface member 'I.F()' + // partial class C : I // 1 + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "I").WithArguments("C", "I.F()").WithLocation(5, 19)); + } + + [Fact] + public void TypeArguments_01() + { + var source = """ + file struct S { public int X; } + class Container { } + unsafe class Program + { + Container M1() => new Container(); // 1 + S[] M2() => new S[0]; // 2 + (S, S) M3() => (new S(), new S()); // 3 + S* M4() => null; // 4 + delegate* M5() => null; // 5 + } + """; + + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyDiagnostics( + // (1,28): warning CS0649: Field 'S.X' is never assigned to, and will always have its default value 0 + // file struct S { public int X; } + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "X").WithArguments("S.X", "0").WithLocation(1, 28), + // (5,18): error CS9051: File type 'Container' cannot be used in a member signature in non-file type 'Program'. + // Container M1() => new Container(); // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M1").WithArguments("Container", "Program").WithLocation(5, 18), + // (6,9): error CS9051: File type 'S[]' cannot be used in a member signature in non-file type 'Program'. + // S[] M2() => new S[0]; // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M2").WithArguments("S[]", "Program").WithLocation(6, 9), + // (7,12): error CS9051: File type '(S, S)' cannot be used in a member signature in non-file type 'Program'. + // (S, S) M3() => (new S(), new S()); // 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M3").WithArguments("(S, S)", "Program").WithLocation(7, 12), + // (8,8): error CS9051: File type 'S*' cannot be used in a member signature in non-file type 'Program'. + // S* M4() => null; // 4 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M4").WithArguments("S*", "Program").WithLocation(8, 8), + // (9,24): error CS9051: File type 'delegate*' cannot be used in a member signature in non-file type 'Program'. + // delegate* M5() => null; // 5 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "M5").WithArguments("delegate*", "Program").WithLocation(9, 24)); + } + + [Fact] + public void Constraints_01() + { + var source = """ + file class C { } + + file class D + { + void M(T t) where T : C { } // ok + } + + class E + { + void M(T t) where T : C { } // 1 + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (10,30): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'E.M(T)'. + // void M(T t) where T : C { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "C").WithArguments("C", "E.M(T)").WithLocation(10, 30)); + } + + [Fact] + public void PrimaryConstructor_01() + { + var source = """ + file class C { } + + record R1(C c); // 1 + record struct R2(C c); // 2 + + file record R3(C c); + file record struct R4(C c); + """; + + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics( + // (3,8): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'R1'. + // record R1(C c); // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "R1").WithArguments("C", "R1").WithLocation(3, 8), + // (3,8): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'R1'. + // record R1(C c); // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "R1").WithArguments("C", "R1").WithLocation(3, 8), + // (4,15): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'R2'. + // record struct R2(C c); // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "R2").WithArguments("C", "R2").WithLocation(4, 15), + // (4,15): error CS9051: File type 'C' cannot be used in a member signature in non-file type 'R2'. + // record struct R2(C c); // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "R2").WithArguments("C", "R2").WithLocation(4, 15) + ); + } + + [Fact] + public void Lambda_01() + { + var source = """ + file class C { } + + class Program + { + void M() + { + var lambda = C (C c) => c; // ok + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void LocalFunction_01() + { + var source = """ + file class C { } + + class Program + { + void M() + { + local(null!); + C local(C c) => c; // ok + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AccessThroughNamespace_01() + { + var source = """ + using System; + + namespace NS + { + file class C + { + public static void M() => Console.Write(1); + } + } + + class Program + { + public static void Main() + { + NS.C.M(); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void AccessThroughNamespace_02() + { + var source1 = """ + using System; + + namespace NS + { + file class C + { + public static void M() + { + Console.Write(1); + } + } + } + """; + + var source2 = """ + class Program + { + static void Main() + { + NS.C.M(); // 1 + } + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (5,9): error CS0234: The type or namespace name 'C' does not exist in the namespace 'NS' (are you missing an assembly reference?) + // NS.C.M(); // 1 + Diagnostic(ErrorCode.ERR_DottedTypeNameNotFoundInNS, "NS.C").WithArguments("C", "NS").WithLocation(5, 9)); + } + + [Fact] + public void AccessThroughType_01() + { + var source = """ + using System; + + class Outer + { + file class C // 1 + { + public static void M() => Console.Write(1); + } + } + + class Program + { + public static void Main() + { + Outer.C.M(); // 2 + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C // 1 + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(5, 16), + // (15,15): error CS0122: 'Outer.C' is inaccessible due to its protection level + // Outer.C.M(); // 2 + Diagnostic(ErrorCode.ERR_BadAccess, "C").WithArguments("Outer.C").WithLocation(15, 15)); + } + + [Fact] + public void AccessThroughType_02() + { + var source1 = """ + using System; + + class Outer + { + file class C + { + public static void M() + { + Console.Write(1); + } + } + } + """; + + var source2 = """ + class Program + { + static void Main() + { + Outer.C.M(); // 1 + } + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (5,15): error CS0117: 'Outer' does not contain a definition for 'C' + // Outer.C.M(); // 1 + Diagnostic(ErrorCode.ERR_NoSuchMember, "C").WithArguments("Outer", "C").WithLocation(5, 15), + // (5,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(5, 16)); + } + + [Fact] + public void AccessThroughGlobalUsing_01() + { + var usings = """ + global using NS; + """; + + var source = """ + using System; + + namespace NS + { + file class C + { + public static void M() => Console.Write(1); + } + } + + class Program + { + public static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { usings, source, IsExternalInitTypeDefinition }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Theory] + [InlineData("file ")] + [InlineData("")] + public void AccessThroughGlobalUsing_02(string fileModifier) + { + var source = $$""" + using System; + + namespace NS + { + {{fileModifier}}class C + { + public static void M() => Console.Write(1); + } + } + + class Program + { + public static void Main() + { + C.M(); // 1 + } + } + """; + + // note: 'Usings' is a legacy setting which only works in scripts. + // https://github.com/dotnet/roslyn/issues/61502 + var compilation = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, options: TestOptions.DebugExe.WithUsings("NS")); + compilation.VerifyDiagnostics( + // (15,9): error CS0103: The name 'C' does not exist in the current context + // C.M(); // 1 + Diagnostic(ErrorCode.ERR_NameNotInContext, "C").WithArguments("C").WithLocation(15, 9)); + } + + [Fact] + public void GlobalUsingStatic_01() + { + var source = """ + global using static C; + + file class C + { + public static void M() { } + } + """; + + var main = """ + class Program + { + public static void Main() + { + M(); + } + } + """; + + var compilation = CreateCompilation(new[] { source, main }); + compilation.VerifyDiagnostics( + // (1,1): hidden CS8019: Unnecessary using directive. + // global using static C; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C;").WithLocation(1, 1), + // (1,21): error CS9055: File type 'C' cannot be used in a 'global using static' directive. + // global using static C; + Diagnostic(ErrorCode.ERR_GlobalUsingStaticFileType, "C").WithArguments("C").WithLocation(1, 21), + // (5,9): error CS0103: The name 'M' does not exist in the current context + // M(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "M").WithArguments("M").WithLocation(5, 9)); + } + + [Fact] + public void GlobalUsingStatic_02() + { + var source = """ + global using static Container; + + public class Container + { + } + + file class C + { + public static void M() { } + } + """; + + var main = """ + class Program + { + public static void Main() + { + M(); + } + } + """; + + var compilation = CreateCompilation(new[] { source, main }); + compilation.VerifyDiagnostics( + // (1,1): hidden CS8019: Unnecessary using directive. + // global using static Container; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static Container;").WithLocation(1, 1), + // (1,21): error CS9055: File type 'Container' cannot be used in a 'global using static' directive. + // global using static Container; + Diagnostic(ErrorCode.ERR_GlobalUsingStaticFileType, "Container").WithArguments("Container").WithLocation(1, 21), + // (5,9): error CS0103: The name 'M' does not exist in the current context + // M(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "M").WithArguments("M").WithLocation(5, 9)); + } + + [Fact] + public void UsingStatic_01() + { + var source = """ + using System; + using static C; + + file class C + { + public static void M() + { + Console.Write(1); + } + } + + class Program + { + public static void Main() + { + M(); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void UsingStatic_02() + { + var source1 = """ + using System; + using static C.D; + + M(); + + file class C + { + public class D + { + public static void M() { Console.Write(1); } + } + } + """; + + var source2 = """ + using System; + + class C + { + public class D + { + public static void M() { Console.Write(2); } + } + } + """; + + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + + var members = comp.GetMembers("C"); + Assert.Equal(2, members.Length); + var expectedMember = ((NamedTypeSymbol)members[0]).GetMember("D.M"); + + var invocation = tree.GetRoot().DescendantNodes().OfType().First(); + var symbolInfo = model.GetSymbolInfo(invocation.Expression); + Assert.Equal(expectedMember.GetPublicSymbol(), symbolInfo.Symbol); + Assert.Equal(0, symbolInfo.CandidateSymbols.Length); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + } + + [Theory] + [InlineData("file ")] + [InlineData("")] + public void UsingStatic_03(string fileModifier) + { + // note: the top-level `class D` "wins" the lookup in this scenario. + var source1 = $$""" + using System; + using static C; + + D.M(); + + {{fileModifier}}class C + { + public class D + { + public static void M() { Console.Write(1); } + } + } + """; + + var source2 = """ + using System; + + class D + { + public static void M() { Console.Write(2); } + } + """; + + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "2"); + verifier.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // using static C; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C;").WithLocation(2, 1)); + var comp = (CSharpCompilation)verifier.Compilation; + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + + var expectedMember = comp.GetMember("D.M"); + + var invocation = tree.GetRoot().DescendantNodes().OfType().First(); + var symbolInfo = model.GetSymbolInfo(invocation.Expression); + Assert.Equal(expectedMember.GetPublicSymbol(), symbolInfo.Symbol); + Assert.Equal(0, symbolInfo.CandidateSymbols.Length); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + } + + [Fact] + public void TypeShadowing() + { + var source = """ + using System; + + class Base + { + internal class C + { + public static void M() + { + Console.Write(1); + } + } + } + + class Derived : Base + { + new file class C + { + } + } + """; + + var main = """ + class Program + { + public static void Main() + { + Derived.C.M(); + } + } + """; + + // 'Derived.C' is not actually accessible from 'Program', so we just bind to 'Base.C'. + var compilation = CreateCompilation(new[] { source, main }); + compilation.VerifyDiagnostics( + // (16,20): error CS9054: File type 'Derived.C' must be defined in a top level type; 'Derived.C' is a nested type. + // new file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Derived.C").WithLocation(16, 20)); + + var expected = compilation.GetMember("Base.C.M"); + + var tree = compilation.SyntaxTrees[1]; + var model = compilation.GetSemanticModel(tree); + var invoked = tree.GetRoot().DescendantNodes().OfType().Single().Expression; + var symbolInfo = model.GetSymbolInfo(invoked); + Assert.Equal(expected, symbolInfo.Symbol.GetSymbol()); + } + + [Fact] + public void SemanticModel_01() + { + var source = """ + namespace NS; + + file class C + { + public static void M() { } + } + + class Program + { + public void M() + { + C.M(); + } + } + """; + + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees[0]; + var body = tree.GetRoot().DescendantNodes().OfType().Last().Body!; + + var model = compilation.GetSemanticModel(tree, ignoreAccessibility: false); + + var info = model.GetSymbolInfo(((ExpressionStatementSyntax)body.Statements.First()).Expression); + Assert.Equal("void NS.C@.M()", info.Symbol.ToTestDisplayString()); + + var classC = compilation.GetMember("NS.C").GetPublicSymbol(); + Assert.Equal("NS.C@", classC.ToTestDisplayString()); + + // lookup with no container + var symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, name: "C"); + Assert.Equal(new[] { classC }, symbols); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition); + Assert.Contains(classC, symbols); + + // lookup with a correct container + var nsSymbol = compilation.GetMember("NS").GetPublicSymbol(); + Assert.Equal("NS", nsSymbol.ToTestDisplayString()); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol, name: "C"); + Assert.Equal(new[] { classC }, symbols); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol); + Assert.Contains(classC, symbols); + + // lookup with an incorrect container + nsSymbol = compilation.GetMember("System").GetPublicSymbol(); + Assert.Equal("System", nsSymbol.ToTestDisplayString()); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol, name: "C"); + Assert.Empty(symbols); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol); + Assert.DoesNotContain(classC, symbols); + } + + [Fact] + public void SemanticModel_02() + { + var source = """ + namespace NS; + + file class C + { + public static void M() { } + } + """; + + var main = """ + namespace NS; + + class Program + { + public void M() + { + C.M(); // 1 + } + } + """; + + var compilation = CreateCompilation(new[] { source, main }); + compilation.VerifyDiagnostics( + // (7,9): error CS0103: The name 'C' does not exist in the current context + // C.M(); // 1 + Diagnostic(ErrorCode.ERR_NameNotInContext, "C").WithArguments("C").WithLocation(7, 9) + ); + + var tree = compilation.SyntaxTrees[1]; + var body = tree.GetRoot().DescendantNodes().OfType().Last().Body!; + + var model = compilation.GetSemanticModel(tree, ignoreAccessibility: false); + + var info = model.GetSymbolInfo(((ExpressionStatementSyntax)body.Statements.First()).Expression); + Assert.Null(info.Symbol); + Assert.Empty(info.CandidateSymbols); + Assert.Equal(CandidateReason.None, info.CandidateReason); + + var classC = compilation.GetMember("NS.C").GetPublicSymbol(); + Assert.Equal("NS.C@", classC.ToTestDisplayString()); + + // lookup with no container + var symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, name: "C"); + Assert.Empty(symbols); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition); + Assert.DoesNotContain(classC, symbols); + + // lookup with a correct container (still don't find the symbol due to lookup occurring in other file) + var nsSymbol = compilation.GetMember("NS").GetPublicSymbol(); + Assert.Equal("NS", nsSymbol.ToTestDisplayString()); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol, name: "C"); + Assert.Empty(symbols); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol); + Assert.DoesNotContain(classC, symbols); + + // lookup with an incorrect container + nsSymbol = compilation.GetMember("System").GetPublicSymbol(); + Assert.Equal("System", nsSymbol.ToTestDisplayString()); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol, name: "C"); + Assert.Empty(symbols); + + symbols = model.LookupSymbols(body.OpenBraceToken.EndPosition, container: nsSymbol); + Assert.DoesNotContain(classC, symbols); + } + + [Fact] + public void Speculation_01() + { + var source = """ + file class C + { + public static void M() { } + } + + class Program + { + public void M() + { + + } + } + """; + + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees[0]; + var body = tree.GetRoot().DescendantNodes().OfType().Last().Body!; + + var model = compilation.GetSemanticModel(tree, ignoreAccessibility: false); + + var newBody = body.AddStatements(SyntaxFactory.ParseStatement("C.M();")); + Assert.True(model.TryGetSpeculativeSemanticModel(position: body.OpenBraceToken.EndPosition, newBody, out var speculativeModel)); + var info = speculativeModel!.GetSymbolInfo(((ExpressionStatementSyntax)newBody.Statements.First()).Expression); + Assert.Equal(compilation.GetMember("C.M").GetPublicSymbol(), info.Symbol); + + var classC = compilation.GetMember("C").GetPublicSymbol(); + var symbols = speculativeModel.LookupSymbols(newBody.OpenBraceToken.EndPosition, name: "C"); + Assert.Equal(new[] { classC }, symbols); + + symbols = speculativeModel.LookupSymbols(newBody.OpenBraceToken.EndPosition); + Assert.Contains(classC, symbols); + } + + [Fact] + public void Speculation_02() + { + var source = """ + file class C + { + public static void M() { } + } + """; + + var main = """ + class Program + { + public void M() + { + + } + } + """; + + var compilation = CreateCompilation(new[] { source, main }); + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees[1]; + var body = tree.GetRoot().DescendantNodes().OfType().Last().Body!; + + var model = compilation.GetSemanticModel(tree, ignoreAccessibility: false); + + var newBody = body.AddStatements(SyntaxFactory.ParseStatement("C.M();")); + Assert.True(model.TryGetSpeculativeSemanticModel(position: body.OpenBraceToken.EndPosition, newBody, out var speculativeModel)); + var info = speculativeModel!.GetSymbolInfo(((ExpressionStatementSyntax)newBody.Statements.First()).Expression); + Assert.Null(info.Symbol); + Assert.Empty(info.CandidateSymbols); + Assert.Equal(CandidateReason.None, info.CandidateReason); + + var symbols = speculativeModel.LookupSymbols(newBody.OpenBraceToken.EndPosition, name: "C"); + Assert.Empty(symbols); + + symbols = speculativeModel.LookupSymbols(newBody.OpenBraceToken.EndPosition); + Assert.DoesNotContain(compilation.GetMember("C").GetPublicSymbol(), symbols); + } + + [Fact] + public void Cref_01() + { + var source = """ + file class C + { + public static void M() { } + } + + class Program + { + /// + /// In the same file as . + /// + public static void M() + { + + } + } + """; + + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)); + compilation.VerifyDiagnostics(); + } + + [Fact] + public void Cref_02() + { + var source = """ + file class C + { + public static void M() { } + } + """; + + var main = """ + class Program + { + /// + /// In a different file than . + /// + public static void M() + { + + } + } + """; + + var compilation = CreateCompilation(new[] { source, main }, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)); + compilation.VerifyDiagnostics( + // (4,45): warning CS1574: XML comment has cref attribute 'C' that could not be resolved + // /// In a different file than . + Diagnostic(ErrorCode.WRN_BadXMLRef, "C").WithArguments("C").WithLocation(4, 45) + ); + } + + [Fact] + public void TopLevelStatements() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void StaticFileClass() + { + var source = """ + using System; + + C.M(); + + static file class C + { + public static void M() + { + Console.Write(1); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ExtensionMethod_01() + { + var source = """ + using System; + + "a".M(); + + static file class C + { + public static void M(this string s) + { + Console.Write(1); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ExtensionMethod_02() + { + var source1 = """ + "a".M(); // 1 + """; + + var source2 = """ + using System; + + static file class C + { + public static void M(this string s) + { + Console.Write(1); + } + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (1,5): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // "a".M(); // 1 + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M").WithArguments("string", "M").WithLocation(1, 5)); + } + + [Fact] + public void ExtensionMethod_03() + { + var source1 = """ + "a".M(); // 1 + """; + + var source2 = """ + using System; + + file class C + { + static class D + { + public static void M(this string s) // 2 + { + Console.Write(1); + } + } + } + """; + + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics( + // (1,5): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // "a".M(); // 1 + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M").WithArguments("string", "M").WithLocation(1, 5), + // (7,28): error CS1109: Extension methods must be defined in a top level static class; D is a nested class + // public static void M(this string s) // 2 + Diagnostic(ErrorCode.ERR_ExtensionMethodsDecl, "M").WithArguments("D").WithLocation(7, 28)); + } + + [Fact] + public void Alias_01() + { + var source = """ + namespace NS; + using C1 = NS.C; + + file class C + { + } + + class D : C1 { } // 1 + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,7): error CS9053: File type 'C' cannot be used as a base type of non-file type 'D'. + // class D : C1 { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeBase, "D").WithArguments("NS.C", "NS.D").WithLocation(8, 7)); + } + + [Fact] + public void SymbolDisplay() + { + var source1 = """ + file class C1 + { + public static void M() { } + } + """; + + var source2 = """ + file class C2 + { + public static void M() { } + } + """; + + var comp = CreateCompilation(new[] + { + SyntaxFactory.ParseSyntaxTree(source1, TestOptions.RegularPreview), + SyntaxFactory.ParseSyntaxTree(source2, TestOptions.RegularPreview, path: "path/to/FileB.cs") + }); + comp.VerifyDiagnostics(); + + var c1 = comp.GetMember("C1"); + var c2 = comp.GetMember("C2"); + Assert.Equal("C1@", c1.ToTestDisplayString()); + Assert.Equal("C2@FileB", c2.ToTestDisplayString()); + + Assert.Equal("void C1@.M()", c1.GetMember("M").ToTestDisplayString()); + Assert.Equal("void C2@FileB.M()", c2.GetMember("M").ToTestDisplayString()); + } + + [Fact] + public void Script_01() + { + var source1 = """ + using System; + + C1.M("a"); + + static file class C1 + { + public static void M(this string s) { } + } + """; + + var comp = CreateSubmission(source1, parseOptions: TestOptions.Script.WithLanguageVersion(LanguageVersion.Preview)); + comp.VerifyDiagnostics( + // (5,19): error CS9054: File type 'C1' must be defined in a top level type; 'C1' is a nested type. + // static file class C1 + Diagnostic(ErrorCode.ERR_FileTypeNested, "C1").WithArguments("C1").WithLocation(5, 19), + // (7,24): error CS1109: Extension methods must be defined in a top level static class; C1 is a nested class + // public static void M(this string s) { } + Diagnostic(ErrorCode.ERR_ExtensionMethodsDecl, "M").WithArguments("C1").WithLocation(7, 24)); + } + + [Fact] + public void SystemVoid_01() + { + var source1 = """ + using System; + + void M(Void v) { } + + namespace System + { + file class Void { } + } + """; + + // https://github.com/dotnet/roslyn/issues/62331 + // Ideally we would give an error about use of System.Void here. + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics( + // (3,6): warning CS8321: The local function 'M' is declared but never used + // void M(Void v) { } + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "M").WithArguments("M").WithLocation(3, 6)); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + + var voidTypeSyntax = tree.GetRoot().DescendantNodes().OfType().Single().Type!; + var typeInfo = model.GetTypeInfo(voidTypeSyntax); + Assert.Equal("System.Void@", typeInfo.Type!.ToDisplayString(SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes))); + } + + [Fact] + public void GetTypeByMetadataName_01() + { + var source1 = """ + file class C { } + """; + + // from source + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics(); + var sourceMember = comp.GetMember("C"); + Assert.Equal("<>F0__C", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(sourceMember, sourceType); + + Assert.Null(comp.GetTypeByMetadataName("<>F0__D")); + Assert.Null(comp.GetTypeByMetadataName("<>F1__C")); + Assert.Null(comp.GetTypeByMetadataName("F0__C")); + Assert.Null(comp.GetTypeByMetadataName("F0__C")); + + // from metadata + var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + var metadataMember = comp2.GetMember("<>F0__C"); + Assert.Equal("<>F0__C", metadataMember.MetadataName); + + var metadataType = comp2.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(metadataMember, metadataType); + } + + [Fact] + public void GetTypeByMetadataName_02() + { + var source1 = """ + file class C { } + """; + + // from source + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics(); + var sourceMember = comp.GetMember("C"); + Assert.Equal("<>F0__C`1", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C`1"); + Assert.Equal(sourceMember, sourceType); + Assert.Null(comp.GetTypeByMetadataName("<>F0__C")); + + // from metadata + var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + + var metadataMember = comp2.GetMember("<>F0__C"); + Assert.Equal("<>F0__C`1", metadataMember.MetadataName); + + var metadataType = comp2.GetTypeByMetadataName("<>F0__C`1"); + Assert.Equal(metadataMember, metadataType); + } + + [Fact] + public void GetTypeByMetadataName_03() + { + var source1 = """ + class Outer + { + file class C { } // 1 + } + """; + + // from source + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics( + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + var sourceMember = comp.GetMember("Outer.C"); + Assert.Equal("<>F0__C", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("Outer.<>F0__C"); + // Note: strictly speaking, it would be reasonable to return the (invalid) nested file type symbol here. + // However, since we don't actually support nested file types, we don't think we need the API to do the additional lookup + // when the requested type is nested, and so we end up giving a null here. + Assert.Null(sourceType); + } + + [Fact] + public void GetTypeByMetadataName_04() + { + var source1 = """ + file class C { } + """; + + var source2 = """ + class C { } + """; + + // from source + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics(); + var sourceMember = comp.GetMembers("C")[0]; + Assert.Equal("<>F0__C", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(sourceMember, sourceType); + + // from metadata + var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + + var metadataMember = comp2.GetMember("<>F0__C"); + Assert.Equal("<>F0__C", metadataMember.MetadataName); + + var metadataType = comp2.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(metadataMember, metadataType); + } + + [CombinatorialData] + [Theory] + public void GetTypeByMetadataName_05(bool firstIsMetadataReference, bool secondIsMetadataReference) + { + var source1 = """ + file class C { } + """; + + // Create two references containing identically-named file types + var ref1 = CreateCompilation(source1, assemblyName: "ref1"); + var ref2 = CreateCompilation(source1, assemblyName: "ref2"); + + var comp = CreateCompilation("", references: new[] + { + firstIsMetadataReference ? ref1.ToMetadataReference() : ref1.EmitToImageReference(), + secondIsMetadataReference ? ref2.ToMetadataReference() : ref2.EmitToImageReference() + }); + comp.VerifyDiagnostics(); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C"); + Assert.Null(sourceType); + + var types = comp.GetTypesByMetadataName("<>F0__C"); + Assert.Equal(2, types.Length); + Assert.Equal(firstIsMetadataReference ? "C@" : "<>F0__C", types[0].ToTestDisplayString()); + Assert.Equal(secondIsMetadataReference ? "C@" : "<>F0__C", types[1].ToTestDisplayString()); + Assert.NotEqual(types[0], types[1]); + } + + [Fact] + public void GetTypeByMetadataName_06() + { + var source1 = """ + file class C { } + file class C { } + """; + + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics( + // (2,12): error CS0101: The namespace '' already contains a definition for 'C' + // file class C { } + Diagnostic(ErrorCode.ERR_DuplicateNameInNS, "C").WithArguments("C", "").WithLocation(2, 12)); + + var sourceType = ((Compilation)comp).GetTypeByMetadataName("<>F0__C"); + Assert.Equal("C@", sourceType.ToTestDisplayString()); + + var types = comp.GetTypesByMetadataName("<>F0__C"); + Assert.Equal(1, types.Length); + Assert.Same(sourceType, types[0]); + } + + [Fact] + public void GetTypeByMetadataName_07() + { + var source1 = """ + file class C { } + """; + + var comp = CreateCompilation(SyntaxFactory.ParseSyntaxTree(source1, options: TestOptions.RegularPreview, path: "path/to/SomeFile.cs")); + comp.VerifyDiagnostics(); + + Assert.Null(comp.GetTypeByMetadataName("<>F0__C")); + Assert.Empty(comp.GetTypesByMetadataName("<>F0__C")); + + Assert.Null(comp.GetTypeByMetadataName("F0__C")); + Assert.Empty(comp.GetTypesByMetadataName("F0__C")); + + var sourceType = ((Compilation)comp).GetTypeByMetadataName("F0__C"); + Assert.Equal("C@SomeFile", sourceType.ToTestDisplayString()); + + var types = comp.GetTypesByMetadataName("F0__C"); + Assert.Equal(1, types.Length); + Assert.Same(sourceType, types[0]); + } + + [Fact] + public void AssociatedSyntaxTree_01() + { + var source = """ + file class C + { + void M(C c) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!; + Assert.Equal("C@", type.ToTestDisplayString()); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.True(type.IsFile); + + var referencingMetadataComp = CreateCompilation("", new[] { comp.ToMetadataReference() }); + type = ((Compilation)referencingMetadataComp).GetTypeByMetadataName("<>F0__C")!; + Assert.Equal("C@", type.ToTestDisplayString()); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.True(type.IsFile); + + var referencingImageComp = CreateCompilation("", new[] { comp.EmitToImageReference() }); + type = ((Compilation)referencingImageComp).GetTypeByMetadataName("<>F0__C")!; + Assert.Equal("<>F0__C", type.ToTestDisplayString()); + Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree); + Assert.False(type.IsFile); + } + + [Fact] + public void AssociatedSyntaxTree_02() + { + var source = """ + class C + { + void M(C c) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!; + Assert.Equal("C", type.ToTestDisplayString()); + Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree); + Assert.False(type.IsFile); + } + + [Fact] + public void AssociatedSyntaxTree_03() + { + var source = """ + file class C + { + void M(C c) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!; + Assert.Equal("C@", type.ToTestDisplayString()); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.True(type.IsFile); + } +} diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs new file mode 100644 index 0000000000000..4526b59797ae9 --- /dev/null +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs @@ -0,0 +1,2975 @@ +// 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 Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests; + +public class FileModifierParsingTests : ParsingTests +{ + public FileModifierParsingTests(ITestOutputHelper output) : base(output) { } + + protected override SyntaxTree ParseTree(string text, CSharpParseOptions? options) + { + return SyntaxFactory.ParseSyntaxTree(text, options ?? TestOptions.Regular); + } + + private void UsingNode(string text, params DiagnosticDescription[] expectedDiagnostics) + { + UsingNode(text, options: null, expectedParsingDiagnostics: expectedDiagnostics); + } + + private new void UsingNode(string text, CSharpParseOptions? options, params DiagnosticDescription[] expectedDiagnostics) + { + UsingNode(text, options, expectedParsingDiagnostics: expectedDiagnostics); + } + + private void UsingNode(string text, CSharpParseOptions? options = null, DiagnosticDescription[]? expectedParsingDiagnostics = null, DiagnosticDescription[]? expectedBindingDiagnostics = null) + { + options ??= TestOptions.RegularPreview; + expectedParsingDiagnostics ??= Array.Empty(); + expectedBindingDiagnostics ??= expectedParsingDiagnostics; + + var tree = UsingTree(text, options); + Validate(text, (CSharpSyntaxNode)tree.GetRoot(), expectedParsingDiagnostics); + + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(expectedBindingDiagnostics); + } + + [Theory] + [InlineData(SyntaxKind.ClassKeyword)] + [InlineData(SyntaxKind.StructKeyword)] + [InlineData(SyntaxKind.InterfaceKeyword)] + [InlineData(SyntaxKind.RecordKeyword)] + [InlineData(SyntaxKind.EnumKeyword)] + public void FileModifier_01(SyntaxKind typeKeyword) + { + UsingNode($$""" + file {{SyntaxFacts.GetText(typeKeyword)}} C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxFacts.GetBaseTypeDeclarationKind(typeKeyword)); + { + N(SyntaxKind.FileKeyword); + N(typeKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(SyntaxKind.ClassKeyword)] + [InlineData(SyntaxKind.StructKeyword)] + [InlineData(SyntaxKind.InterfaceKeyword)] + [InlineData(SyntaxKind.RecordKeyword)] + public void FileModifier_02(SyntaxKind typeKeyword) + { + UsingNode($$""" + file partial {{SyntaxFacts.GetText(typeKeyword)}} C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxFacts.GetBaseTypeDeclarationKind(typeKeyword)); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PartialKeyword); + N(typeKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_02_Enum() + { + UsingNode($$""" + file partial enum C { } + """, + expectedParsingDiagnostics: new[] + { + // (1,6): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + // file partial enum C { } + Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(1, 6) + }, + // note: we also get duplicate ERR_PartialMisplaced diagnostics on `partial enum C { }`. + expectedBindingDiagnostics: new[] + { + // (1,6): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + // file partial enum C { } + Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(1, 6), + // (1,19): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + // file partial enum C { } + Diagnostic(ErrorCode.ERR_PartialMisplaced, "C").WithLocation(1, 19) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.EnumDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.EnumKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(SyntaxKind.ClassKeyword)] + [InlineData(SyntaxKind.StructKeyword)] + [InlineData(SyntaxKind.InterfaceKeyword)] + public void FileModifier_03(SyntaxKind typeKeyword) + { + UsingNode($$""" + partial file {{SyntaxFacts.GetText(typeKeyword)}} C { } + """, + expectedParsingDiagnostics: new[] + { + // (1,14): error CS1002: ; expected + // partial file {{SyntaxFacts.GetText(typeKeyword)}} C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, SyntaxFacts.GetText(typeKeyword)).WithLocation(1, 14) + }, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0246: The type or namespace name 'partial' could not be found (are you missing a using directive or an assembly reference?) + // partial file interface C { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "partial").WithArguments("partial").WithLocation(1, 1), + // (1,9): warning CS0168: The variable 'file' is declared but never used + // partial file interface C { } + Diagnostic(ErrorCode.WRN_UnreferencedVar, "file").WithArguments("file").WithLocation(1, 9), + // (1,14): error CS1002: ; expected + // partial file {{SyntaxFacts.GetText(typeKeyword)}} C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, SyntaxFacts.GetText(typeKeyword)).WithLocation(1, 14) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "partial"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + M(SyntaxKind.SemicolonToken); + } + } + N(SyntaxFacts.GetBaseTypeDeclarationKind(typeKeyword)); + { + N(typeKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(SyntaxKind.RecordKeyword)] + public void FileModifier_04(SyntaxKind typeKeyword) + { + UsingNode($$""" + partial file {{SyntaxFacts.GetText(typeKeyword)}} C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxFacts.GetBaseTypeDeclarationKind(typeKeyword)); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.FileKeyword); + N(typeKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_05() + { + UsingNode($$""" + file partial record struct C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_06() + { + UsingNode($$""" + partial file record struct C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.FileKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_07_CSharp10() + { + UsingNode($$""" + file partial ref struct C { } + """, + options: TestOptions.Regular10, + expectedParsingDiagnostics: new[] + { + // (1,14): error CS1003: Syntax error, ',' expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 14), + // (1,18): error CS1002: ; expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 18) + }, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(1, 1), + // (1,6): warning CS0168: The variable 'partial' is declared but never used + // file partial ref struct C { } + Diagnostic(ErrorCode.WRN_UnreferencedVar, "partial").WithArguments("partial").WithLocation(1, 6), + // (1,14): error CS1003: Syntax error, ',' expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 14), + // (1,18): error CS1002: ; expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 18) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "partial"); + } + } + M(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_07() + { + UsingNode($$""" + file partial ref struct C { } + """, + expectedParsingDiagnostics: new[] + { + // (1,14): error CS1003: Syntax error, ',' expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 14), + // (1,18): error CS1002: ; expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 18) + }, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(1, 1), + // (1,6): warning CS0168: The variable 'partial' is declared but never used + // file partial ref struct C { } + Diagnostic(ErrorCode.WRN_UnreferencedVar, "partial").WithArguments("partial").WithLocation(1, 6), + // (1,14): error CS1003: Syntax error, ',' expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 14), + // (1,18): error CS1002: ; expected + // file partial ref struct C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 18) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "partial"); + } + } + M(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_08() + { + UsingNode($$""" + partial file ref struct C { } + """, + expectedParsingDiagnostics: new[] + { + // (1,14): error CS1003: Syntax error, ',' expected + // partial file ref struct C { } + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 14), + // (1,18): error CS1002: ; expected + // partial file ref struct C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 18) + }, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0246: The type or namespace name 'partial' could not be found (are you missing a using directive or an assembly reference?) + // partial file ref struct C { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "partial").WithArguments("partial").WithLocation(1, 1), + // (1,9): warning CS0168: The variable 'file' is declared but never used + // partial file ref struct C { } + Diagnostic(ErrorCode.WRN_UnreferencedVar, "file").WithArguments("file").WithLocation(1, 9), + // (1,14): error CS1003: Syntax error, ',' expected + // partial file ref struct C { } + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 14), + // (1,18): error CS1002: ; expected + // partial file ref struct C { } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 18) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "partial"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + M(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_09() + { + UsingNode($$""" + file abstract class C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.AbstractKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_10() + { + UsingNode($$""" + abstract file class C { } + """); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.AbstractKeyword); + N(SyntaxKind.FileKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(SyntaxKind.ClassKeyword)] + [InlineData(SyntaxKind.StructKeyword)] + [InlineData(SyntaxKind.InterfaceKeyword)] + [InlineData(SyntaxKind.RecordKeyword)] + [InlineData(SyntaxKind.EnumKeyword)] + public void FileModifier_11(SyntaxKind typeKeyword) + { + UsingNode($$""" + public file {{SyntaxFacts.GetText(typeKeyword)}} C { } + """, + expectedBindingDiagnostics: new[] + { + // (1,20): error CS9052: File type 'C' cannot use accessibility modifiers. + // public file {{SyntaxFacts.GetText(typeKeyword)}} C { } + Diagnostic(ErrorCode.ERR_FileTypeNoExplicitAccessibility, "C").WithArguments("C") + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxFacts.GetBaseTypeDeclarationKind(typeKeyword)); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.FileKeyword); + N(typeKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(SyntaxKind.ClassKeyword)] + [InlineData(SyntaxKind.StructKeyword)] + [InlineData(SyntaxKind.InterfaceKeyword)] + [InlineData(SyntaxKind.RecordKeyword)] + [InlineData(SyntaxKind.EnumKeyword)] + public void FileModifier_12(SyntaxKind typeKeyword) + { + UsingNode($$""" + file public {{SyntaxFacts.GetText(typeKeyword)}} C { } + """, + expectedBindingDiagnostics: new[] + { + // (1,19): error CS9052: File type 'C' cannot use accessibility modifiers. + // file public {{SyntaxFacts.GetText(typeKeyword)}} C { } + Diagnostic(ErrorCode.ERR_FileTypeNoExplicitAccessibility, "C").WithArguments("C") + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxFacts.GetBaseTypeDeclarationKind(typeKeyword)); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PublicKeyword); + N(typeKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_13() + { + UsingNode(""" + file class C { } + """, + options: TestOptions.Regular10, + expectedBindingDiagnostics: new[] + { + // (1,12): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // file class C { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(1, 12) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_14() + { + UsingNode(""" + file delegate void D(); + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.DelegateDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "D"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_15() + { + UsingNode(""" + namespace NS + { + file class C { } + } + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "NS"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_16() + { + UsingNode(""" + namespace NS; + file class C { } + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.FileScopedNamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "NS"); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_17() + { + UsingNode(""" + class Outer + { + file class C { } + } + """, + expectedBindingDiagnostics: new[] + { + // (3,16): error CS9054: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "Outer"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileModifier_18() + { + UsingNode(""" + class C + { + file delegate* M(); + } + """, + expectedBindingDiagnostics: new[] + { + // (3,10): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // file delegate* M(); + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "delegate*").WithLocation(3, 10), + // (3,31): error CS0106: The modifier 'file' is not valid for this item + // file delegate* M(); + Diagnostic(ErrorCode.ERR_BadMemberFlag, "M").WithArguments("file").WithLocation(3, 31), + // (3,31): error CS0501: 'C.M()' must declare a body because it is not marked abstract, extern, or partial + // file delegate* M(); + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "M").WithArguments("C.M()").WithLocation(3, 31) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.FunctionPointerType); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.AsteriskToken); + N(SyntaxKind.FunctionPointerParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.FunctionPointerParameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.FunctionPointerParameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileMember_01() + { + UsingNode(""" + class C + { + file void M() { } + } + """, + expectedBindingDiagnostics: new[] + { + // (3,15): error CS0106: The modifier 'file' is not valid for this item + // file void M() { } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "M").WithArguments("file").WithLocation(3, 15) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileMember_02() + { + UsingNode(""" + class C + { + file int x; + } + """, + expectedBindingDiagnostics: new[] + { + // (3,14): error CS0106: The modifier 'file' is not valid for this item + // file int x; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "x").WithArguments("file").WithLocation(3, 14), + // (3,14): warning CS0169: The field 'C.x' is never used + // file int x; + Diagnostic(ErrorCode.WRN_UnreferencedField, "x").WithArguments("C.x").WithLocation(3, 14) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileMember_03() + { + UsingNode($$""" + class C + { + file event Action x; + } + """, + expectedBindingDiagnostics: new[] + { + // (3,16): error CS0246: The type or namespace name 'Action' could not be found (are you missing a using directive or an assembly reference?) + // file event Action x; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Action").WithArguments("Action").WithLocation(3, 16), + // (3,23): error CS0106: The modifier 'file' is not valid for this item + // file event Action x; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "x").WithArguments("file").WithLocation(3, 23), + // (3,23): warning CS0067: The event 'C.x' is never used + // file event Action x; + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "x").WithArguments("C.x").WithLocation(3, 23) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.EventFieldDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.EventKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Action"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileMember_04() + { + var source = $$""" + class C + { + file int x { get; set; } + } + """; + + UsingNode(source, expectedBindingDiagnostics: new[] + { + // (3,14): error CS0106: The modifier 'file' is not valid for this item + // file int x { get; set; } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "x").WithArguments("file").WithLocation(3, 14) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.SetAccessorDeclaration); + { + N(SyntaxKind.SetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileMember_05() + { + var source = $$""" + class C + { + async file void M() { } + } + """; + + UsingNode(source, expectedBindingDiagnostics: new[] + { + // (3,21): error CS0106: The modifier 'file' is not valid for this item + // async file void M() { } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "M").WithArguments("file").WithLocation(3, 21), + // (3,21): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async file void M() { } + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "M").WithLocation(3, 21) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.FileKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_01() + { + UsingNode($$""" + class C + { + int file; + } + """, expectedBindingDiagnostics: new[] + { + // (3,9): warning CS0169: The field 'C.file' is never used + // int file; + Diagnostic(ErrorCode.WRN_UnreferencedField, "file").WithArguments("C.file").WithLocation(3, 9) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_02() + { + UsingNode($$""" + class C + { + int file { get; set; } + } + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "file"); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.SetAccessorDeclaration); + { + N(SyntaxKind.SetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_03() + { + UsingNode($$""" + class C + { + event Action file; + } + """, + expectedBindingDiagnostics: new[] + { + // (3,11): error CS0246: The type or namespace name 'Action' could not be found (are you missing a using directive or an assembly reference?) + // event Action file; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Action").WithArguments("Action").WithLocation(3, 11), + // (3,18): warning CS0067: The event 'C.file' is never used + // event Action file; + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "file").WithArguments("C.file").WithLocation(3, 18) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.EventFieldDeclaration); + { + N(SyntaxKind.EventKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Action"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_04() + { + UsingNode($$""" + class C + { + void file() { } + } + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "file"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_05() + { + UsingNode($$""" + file class file { } + """, + expectedBindingDiagnostics: new[] + { + // (1,12): error CS9056: Types and aliases cannot be named 'file'. + // file class file { } + Diagnostic(ErrorCode.ERR_FileTypeNameDisallowed, "file").WithLocation(1, 12) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "file"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_06_CSharp10() + { + UsingNode($$""" + class C + { + file async; + } + """, + options: TestOptions.Regular10, + expectedBindingDiagnostics: new[] + { + // (3,5): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file async; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(3, 5), + // (3,10): warning CS0169: The field 'C.async' is never used + // file async; + Diagnostic(ErrorCode.WRN_UnreferencedField, "async").WithArguments("C.async").WithLocation(3, 10) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "async"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_06() + { + UsingNode($$""" + class C + { + file async; + } + """, + // (3,15): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // file async; + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(3, 15), + // (3,15): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // file async; + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(3, 15)); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "async"); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_07_CSharp10() + { + UsingNode($$""" + class C + { + file item; + } + """, + options: TestOptions.Regular10, + expectedBindingDiagnostics: new[] + { + // (3,5): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file item; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(3, 5), + // (3,10): warning CS0169: The field 'C.item' is never used + // file item; + Diagnostic(ErrorCode.WRN_UnreferencedField, "item").WithArguments("C.item").WithLocation(3, 10) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "item"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_07() + { + UsingNode($$""" + class C + { + file item; + } + """, + expectedParsingDiagnostics: new[] + { + // (3,14): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // file item; + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(3, 14), + // (3,14): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // file item; + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(3, 14) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "item"); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MemberNamedFile_08() + { + UsingNode($$""" + record file { } + """, + expectedBindingDiagnostics: new[] + { + // (1,8): error CS9056: Types and aliases cannot be named 'file'. + // record file { } + Diagnostic(ErrorCode.ERR_FileTypeNameDisallowed, "file").WithLocation(1, 8) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "file"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void TypeNamedFile_01() + { + UsingNode($$""" + class file { } + """, + expectedBindingDiagnostics: new[] + { + // (1,7): error CS9056: Types and aliases cannot be named 'file'. + // class file { } + Diagnostic(ErrorCode.ERR_FileTypeNameDisallowed, "file").WithLocation(1, 7) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "file"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void TypeNamedFile_01_CSharp10() + { + UsingNode($$""" + class file { } + """, + options: TestOptions.Regular10, + expectedBindingDiagnostics: new[] + { + // (1,7): warning CS8981: The type name 'file' only contains lower-cased ascii characters. Such names may become reserved for the language. + // class file { } + Diagnostic(ErrorCode.WRN_LowerCaseTypeName, "file").WithArguments("file").WithLocation(1, 7) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "file"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersionFacts.CSharpNext)] + public void TypeNamedFile_02(LanguageVersion languageVersion) + { + UsingNode($$""" + class @file { } + """, + options: TestOptions.Regular.WithLanguageVersion(languageVersion)); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "@file"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_01_CSharp10() + { + UsingNode($$""" + file + """, + options: TestOptions.Regular10, + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1)); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_01() + { + UsingNode($$""" + file + """, + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1)); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_02_CSharp10() + { + UsingNode($$""" + file; + """, + options: TestOptions.Regular10, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0103: The name 'file' does not exist in the current context + // file; + Diagnostic(ErrorCode.ERR_NameNotInContext, "file").WithArguments("file").WithLocation(1, 1), + // (1,1): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // file; + Diagnostic(ErrorCode.ERR_IllegalStatement, "file").WithLocation(1, 1) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_02() + { + UsingNode($$""" + file; + """, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0103: The name 'file' does not exist in the current context + // file; + Diagnostic(ErrorCode.ERR_NameNotInContext, "file").WithArguments("file").WithLocation(1, 1), + // (1,1): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // file; + Diagnostic(ErrorCode.ERR_IllegalStatement, "file").WithLocation(1, 1) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_03_CSharp10() + { + UsingNode($$""" + file namespace NS; + """, + options: TestOptions.Regular10, + expectedParsingDiagnostics: new[] + { + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file namespace NS; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1) + }, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file namespace NS; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1), + // (1,16): error CS8956: File-scoped namespace must precede all other members in a file. + // file namespace NS; + Diagnostic(ErrorCode.ERR_FileScopedNamespaceNotBeforeAllMembers, "NS").WithLocation(1, 16) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.FileScopedNamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "NS"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_03() + { + UsingNode($$""" + file namespace NS; + """, + expectedParsingDiagnostics: new[] + { + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file namespace NS; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1) + }, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file namespace NS; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1), + // (1,16): error CS8956: File-scoped namespace must precede all other members in a file. + // file namespace NS; + Diagnostic(ErrorCode.ERR_FileScopedNamespaceNotBeforeAllMembers, "NS").WithLocation(1, 16) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.FileScopedNamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "NS"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_04_CSharp10() + { + UsingNode($$""" + file namespace NS { } + """, + options: TestOptions.Regular10, + expectedParsingDiagnostics: new[] + { + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file namespace NS { } + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "NS"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Errors_04() + { + UsingNode($$""" + file namespace NS { } + """, + expectedParsingDiagnostics: new[] + { + // (1,1): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // file namespace NS { } + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 1) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "NS"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void File_Repeated() + { + const int FileModifiersCount = 100000; + var manyFileModifiers = string.Join(" ", Enumerable.Repeat("file", FileModifiersCount)); + UsingNode(manyFileModifiers, + expectedParsingDiagnostics: new[] + { + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "file").WithLocation(1, 499996) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + for (var i = 0; i < FileModifiersCount - 1; i++) + { + N(SyntaxKind.FileKeyword); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.EndOfFileToken); + } + } + EOF(); + + UsingNode(manyFileModifiers + " class { }", + expectedParsingDiagnostics: new[] + { + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 500007) + }, + expectedBindingDiagnostics: new[] + { + Diagnostic(ErrorCode.ERR_DuplicateModifier, "file").WithArguments("file").WithLocation(1, 6), + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 500007) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + for (int i = 0; i < FileModifiersCount; i++) + { + N(SyntaxKind.FileKeyword); + } + N(SyntaxKind.ClassKeyword); + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MethodNamedRecord_01_CSharp8() + { + UsingNode(""" + class C + { + file record(); + } + """, + options: TestOptions.Regular8, + expectedBindingDiagnostics: new[] + { + // (3,5): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file record(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(3, 5), + // (3,10): error CS0501: 'C.record()' must declare a body because it is not marked abstract, extern, or partial + // file record(); + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "record").WithArguments("C.record()").WithLocation(3, 10) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.IdentifierToken, "record"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MethodNamedRecord_01_CSharpNext() + { + UsingNode(""" + class C + { + file record(); + } + """, + expectedBindingDiagnostics: new[] + { + // (3,10): error CS0106: The modifier 'file' is not valid for this item + // file record(); + Diagnostic(ErrorCode.ERR_BadMemberFlag, "record").WithArguments("file").WithLocation(3, 10), + // (3,10): error CS1520: Method must have a return type + // file record(); + Diagnostic(ErrorCode.ERR_MemberNeedsType, "record").WithLocation(3, 10) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ConstructorDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.IdentifierToken, "record"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MethodNamedRecord_02_CSharp8() + { + UsingNode(""" + class C + { + file record() { } + } + """, + options: TestOptions.Regular8, + expectedBindingDiagnostics: new[] + { + // (3,5): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file record() { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(3, 5), + // (3,10): error CS0161: 'C.record()': not all code paths return a value + // file record() { } + Diagnostic(ErrorCode.ERR_ReturnExpected, "record").WithArguments("C.record()").WithLocation(3, 10) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.IdentifierToken, "record"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void MethodNamedRecord_02_CSharpNext() + { + UsingNode(""" + class C + { + file record() { } + } + """, expectedBindingDiagnostics: new[] + { + // (3,10): error CS0106: The modifier 'file' is not valid for this item + // file record() { } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "record").WithArguments("file").WithLocation(3, 10), + // (3,10): error CS1520: Method must have a return type + // file record() { } + Diagnostic(ErrorCode.ERR_MemberNeedsType, "record").WithLocation(3, 10) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ConstructorDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.IdentifierToken, "record"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_01_CSharp8() + { + UsingNode(""" + class C + { + file record X(); + } + """, + options: TestOptions.Regular8, + expectedBindingDiagnostics: new[] + { + // (3,10): error CS0246: The type or namespace name 'record' could not be found (are you missing a using directive or an assembly reference?) + // file record X(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "record").WithArguments("record").WithLocation(3, 10), + // (3,17): error CS0106: The modifier 'file' is not valid for this item + // file record X(); + Diagnostic(ErrorCode.ERR_BadMemberFlag, "X").WithArguments("file").WithLocation(3, 17), + // (3,17): error CS0501: 'C.X()' must declare a body because it is not marked abstract, extern, or partial + // file record X(); + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "X").WithArguments("C.X()").WithLocation(3, 17) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_01_CSharpNext() + { + UsingNode(""" + class C + { + file record X(); + } + """, + expectedBindingDiagnostics: new[] + { + // (3,17): error CS9054: File type 'C.X' must be defined in a top level type; 'C.X' is a nested type. + // file record X(); + Diagnostic(ErrorCode.ERR_FileTypeNested, "X").WithArguments("C.X").WithLocation(3, 17) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_02_CSharp8() + { + UsingNode(""" + class C + { + file record X() { } + } + """, + options: TestOptions.Regular8, + expectedBindingDiagnostics: new[] + { + // (3,10): error CS0246: The type or namespace name 'record' could not be found (are you missing a using directive or an assembly reference?) + // file record X() { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "record").WithArguments("record").WithLocation(3, 10), + // (3,17): error CS0106: The modifier 'file' is not valid for this item + // file record X() { } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "X").WithArguments("file").WithLocation(3, 17), + // (3,17): error CS0161: 'C.X()': not all code paths return a value + // file record X() { } + Diagnostic(ErrorCode.ERR_ReturnExpected, "X").WithArguments("C.X()").WithLocation(3, 17) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_02_CSharpNext() + { + UsingNode(""" + class C + { + file record X() { } + } + """, + expectedBindingDiagnostics: new[] + { + // (3,17): error CS9054: File type 'C.X' must be defined in a top level type; 'C.X' is a nested type. + // file record X() { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "X").WithArguments("C.X").WithLocation(3, 17) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_03_CSharp8() + { + UsingNode(""" + class C + { + file record X; + } + """, + options: TestOptions.Regular8, expectedBindingDiagnostics: new[] + { + // (3,10): error CS0246: The type or namespace name 'record' could not be found (are you missing a using directive or an assembly reference?) + // file record X; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "record").WithArguments("record").WithLocation(3, 10), + // (3,17): error CS0106: The modifier 'file' is not valid for this item + // file record X; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "X").WithArguments("file").WithLocation(3, 17), + // (3,17): warning CS0169: The field 'C.X' is never used + // file record X; + Diagnostic(ErrorCode.WRN_UnreferencedField, "X").WithArguments("C.X").WithLocation(3, 17) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_03_CSharpNext() + { + UsingNode(""" + class C + { + file record X; + } + """, + expectedBindingDiagnostics: new[] + { + // (3,17): error CS9054: File type 'C.X' must be defined in a top level type; 'C.X' is a nested type. + // file record X; + Diagnostic(ErrorCode.ERR_FileTypeNested, "X").WithArguments("C.X").WithLocation(3, 17) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void FileRecord_04_CSharpNext() + { + UsingNode(""" + file record X(); + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void LocalVariable_01() + { + UsingNode(""" + void M() + { + file file; + } + """, + expectedBindingDiagnostics: new[] + { + // (1,6): warning CS8321: The local function 'M' is declared but never used + // void M() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "M").WithArguments("M").WithLocation(1, 6), + // (3,5): error CS0118: 'file' is a variable but is used like a type + // file file; + Diagnostic(ErrorCode.ERR_BadSKknown, "file").WithArguments("file", "variable", "type").WithLocation(3, 5), + // (3,10): warning CS0168: The variable 'file' is declared but never used + // file file; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "file").WithArguments("file").WithLocation(3, 10) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void LocalVariable_02() + { + UsingNode(""" + void M() + { + int file; + } + """, + expectedBindingDiagnostics: new[] + { + // (1,6): warning CS8321: The local function 'M' is declared but never used + // void M() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "M").WithArguments("M").WithLocation(1, 6), + // (3,9): warning CS0168: The variable 'file' is declared but never used + // int file; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "file").WithArguments("file").WithLocation(3, 9) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersionFacts.CSharpNext)] + public void TopLevelVariable_01(LanguageVersion languageVersion) + { + UsingNode(""" + file file; + """, + options: TestOptions.Regular.WithLanguageVersion(languageVersion), + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0118: 'file' is a variable but is used like a type + // file file; + Diagnostic(ErrorCode.ERR_BadSKknown, "file").WithArguments("file", "variable", "type").WithLocation(1, 1), + // (1,6): warning CS0168: The variable 'file' is declared but never used + // file file; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "file").WithArguments("file").WithLocation(1, 6) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersionFacts.CSharpNext)] + public void TopLevelVariable_02(LanguageVersion languageVersion) + { + UsingNode(""" + int file; + """, + options: TestOptions.Regular.WithLanguageVersion(languageVersion), + expectedBindingDiagnostics: new[] + { + // (1,5): warning CS0168: The variable 'file' is declared but never used + // int file; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "file").WithArguments("file").WithLocation(1, 5) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersionFacts.CSharpNext)] + public void TopLevelVariable_03(LanguageVersion languageVersion) + { + UsingNode(""" + bool file; + file = true; + """, + options: TestOptions.Regular.WithLanguageVersion(languageVersion), + expectedBindingDiagnostics: new[] + { + // (1,6): warning CS0219: The variable 'file' is assigned but its value is never used + // bool file; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "file").WithArguments("file").WithLocation(1, 6) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.BoolKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void Variable_01() + { + UsingNode(""" + void M() + { + bool file; + file = true; + } + """, + expectedBindingDiagnostics: new[] + { + // (1,6): warning CS8321: The local function 'M' is declared but never used + // void M() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "M").WithArguments("M").WithLocation(1, 6), + // (3,10): warning CS0219: The variable 'file' is assigned but its value is never used + // bool file; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "file").WithArguments("file").WithLocation(3, 10) + }); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.BoolKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "file"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void LambdaReturn() + { + UsingNode(""" + _ = file () => { }; + """, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS8183: Cannot infer the type of implicitly-typed discard. + // _ = file () => { }; + Diagnostic(ErrorCode.ERR_DiscardTypeInferenceFailed, "_").WithLocation(1, 1), + // (1,5): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // _ = file () => { }; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(1, 5) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "_"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void LocalFunctionReturn() + { + UsingNode(""" + file local() { }; + """, + expectedBindingDiagnostics: new[] + { + // (1,1): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // file local() { }; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(1, 1), + // (1,6): error CS0161: 'local()': not all code paths return a value + // file local() { }; + Diagnostic(ErrorCode.ERR_ReturnExpected, "local").WithArguments("local()").WithLocation(1, 6), + // (1,6): warning CS8321: The local function 'local' is declared but never used + // file local() { }; + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "local").WithArguments("local").WithLocation(1, 6) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.IdentifierToken, "local"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void ParameterModifier() + { + UsingNode(""" + class C + { + void M(file int x) { } + } + """, + expectedParsingDiagnostics: new[] + { + // (3,17): error CS1001: Identifier expected + // void M(file int x) { } + Diagnostic(ErrorCode.ERR_IdentifierExpected, "int").WithLocation(3, 17), + // (3,17): error CS1003: Syntax error, ',' expected + // void M(file int x) { } + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(3, 17) + }, + expectedBindingDiagnostics: new[] + { + // (3,12): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // void M(file int x) { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(3, 12), + // (3,17): error CS1001: Identifier expected + // void M(file int x) { } + Diagnostic(ErrorCode.ERR_IdentifierExpected, "int").WithLocation(3, 17), + // (3,17): error CS1003: Syntax error, ',' expected + // void M(file int x) { } + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(3, 17) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void ParameterType() + { + UsingNode(""" + class C + { + void M(file x) { } + } + """, + expectedBindingDiagnostics: new[] + { + // (3,12): error CS0246: The type or namespace name 'file' could not be found (are you missing a using directive or an assembly reference?) + // void M(file x) { } + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "file").WithArguments("file").WithLocation(3, 12) + }); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "file"); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } +} diff --git a/src/Compilers/Core/CodeAnalysisTest/CorLibTypesTests.cs b/src/Compilers/Core/CodeAnalysisTest/CorLibTypesTests.cs index 37ff9a1074120..f827959ab0bc0 100644 --- a/src/Compilers/Core/CodeAnalysisTest/CorLibTypesTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/CorLibTypesTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -191,10 +192,9 @@ public void ConstantValueGetHashCodeTest02() [Fact] public void ConstantValueToStringTest01() { - var value = "Null"; -#if NETCOREAPP - value = "Nothing"; -#endif + string value = (RuntimeUtilities.IsCoreClrRuntime && !RuntimeUtilities.IsCoreClr6Runtime) + ? "Nothing" + : "Null"; var cv = ConstantValue.Create(null, ConstantValueTypeDiscriminator.Null); Assert.Equal($"ConstantValueNull(null: {value})", cv.ToString()); diff --git a/src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerAssemblyLoaderTests.cs b/src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerAssemblyLoaderTests.cs index 7102c7654bce3..02525eb8c552d 100644 --- a/src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerAssemblyLoaderTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerAssemblyLoaderTests.cs @@ -139,7 +139,7 @@ public void AssemblyLoading_DependencyInDifferentDirectory_Delete() File.Delete(gammaFile.Path); var actual = sb.ToString(); - Assert.Equal(@"Delta.2: Gamma: Test G + Assert.Equal(@"Delta: Gamma: Test G ", actual); } } diff --git a/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs b/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs index b0cb635e5360a..084f15f4153f2 100644 --- a/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs +++ b/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs @@ -68,7 +68,7 @@ public ImmutableDictionary GetMethodsByName() } private static readonly SymbolDisplayFormat _testDataKeyFormat = new SymbolDisplayFormat( - compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames | SymbolDisplayCompilerInternalOptions.UseValueTuple, + compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames | SymbolDisplayCompilerInternalOptions.UseValueTuple | SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes, globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance, diff --git a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs index e65375f0206bf..6d97be32cc7ab 100644 --- a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs +++ b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs @@ -596,6 +596,8 @@ public TypeDefinitionHandle TypeDef public bool MangleName => false; + public string? AssociatedFileIdentifier => null; + public virtual ushort Alignment => 0; public virtual Cci.ITypeReference GetBaseClass(EmitContext context) diff --git a/src/Compilers/Core/Portable/CodeGen/StateMachineState.cs b/src/Compilers/Core/Portable/CodeGen/StateMachineState.cs new file mode 100644 index 0000000000000..4143c648836ba --- /dev/null +++ b/src/Compilers/Core/Portable/CodeGen/StateMachineState.cs @@ -0,0 +1,46 @@ +// 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; + +internal enum StateMachineState +{ + /// + /// First state of an async iterator state machine that is used to resume the machine after yield return. + /// Initial state is not used to resume state machine that yielded. State numbers decrease as the iterator makes progress. + /// + FirstResumableAsyncIteratorState = InitialAsyncIteratorState - 1, + + /// + /// Initial iterator state of an async iterator. + /// Distinct from so that DisposeAsync can throw in latter case. + /// + InitialAsyncIteratorState = -3, + + /// + /// First state of an iterator state machine. State numbers decrease for subsequent finalize states. + /// + FirstIteratorFinalizeState = -3, + + FinishedState = -2, + NotStartedOrRunningState = -1, + FirstUnusedState = 0, + + /// + /// First state in async state machine that is used to resume the machine after await. + /// State numbers increase as the async computation makes progress. + /// + FirstResumableAsyncState = 0, + + /// + /// Initial iterator state of an iterator. + /// + InitialIteratorState = 0, + + /// + /// First state in iterator state machine that is used to resume the machine after yield return. + /// Initial state is not used to resume state machine that yielded. State numbers increase as the iterator makes progress. + /// + FirstResumableIteratorState = InitialIteratorState + 1, +} diff --git a/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs b/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs index e772df5aab45e..e4196126666fb 100644 --- a/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs +++ b/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs @@ -5,28 +5,49 @@ using System; using System.Collections.Immutable; using System.Linq; +using Microsoft.CodeAnalysis.Emit; namespace Microsoft.CodeAnalysis.CodeGen; internal readonly struct StateMachineStateDebugInfo { public readonly int SyntaxOffset; - public readonly int StateNumber; + public readonly StateMachineState StateNumber; - public StateMachineStateDebugInfo(int syntaxOffset, int stateNumber) + public StateMachineStateDebugInfo(int syntaxOffset, StateMachineState stateNumber) { SyntaxOffset = syntaxOffset; StateNumber = stateNumber; } } +/// +/// Debug information maintained for each state machine. +/// Facilitates mapping of state machine states from a compilation to the previous one (or to a metadata baseline) during EnC. +/// internal readonly struct StateMachineStatesDebugInfo { public readonly ImmutableArray States; - public readonly int? FirstUnusedIncreasingStateMachineState; - public readonly int? FirstUnusedDecreasingStateMachineState; - public StateMachineStatesDebugInfo(ImmutableArray states, int? firstUnusedIncreasingStateMachineState, int? firstUnusedDecreasingStateMachineState) + /// + /// The number of the first state that has not been used in any of the previous versions of the state machine, + /// or null if we are not generating EnC delta. + /// + /// For 1st generation EnC delta, this is calculated by examining the stored in the baseline metadata. + /// For subsequent generations, the number is updated to account for newly generated states in that generation. + /// + public readonly StateMachineState? FirstUnusedIncreasingStateMachineState; + + /// + /// The number of the first state that has not been used in any of the previous versions of the state machine, + /// or null if we are not generating EnC delta, or the state machine has no decreasing states. + /// + /// For 1st generation EnC delta, this is calculated by examining the stored in the baseline metadata. + /// For subsequent generations, the number is updated to account for newly generated states in that generation. + /// + public readonly StateMachineState? FirstUnusedDecreasingStateMachineState; + + private StateMachineStatesDebugInfo(ImmutableArray states, StateMachineState? firstUnusedIncreasingStateMachineState, StateMachineState? firstUnusedDecreasingStateMachineState) { States = states; FirstUnusedIncreasingStateMachineState = firstUnusedIncreasingStateMachineState; @@ -35,7 +56,7 @@ public StateMachineStatesDebugInfo(ImmutableArray st public static StateMachineStatesDebugInfo Create(VariableSlotAllocator? variableSlotAllocator, ImmutableArray stateInfos) { - int? firstUnusedIncreasingStateMachineState = null, firstUnusedDecreasingStateMachineState = null; + StateMachineState? firstUnusedIncreasingStateMachineState = null, firstUnusedDecreasingStateMachineState = null; if (variableSlotAllocator != null) { @@ -51,11 +72,11 @@ public static StateMachineStatesDebugInfo Create(VariableSlotAllocator? variable var maxState = stateInfos.Max(info => info.StateNumber) + 1; var minState = stateInfos.Min(info => info.StateNumber) - 1; - firstUnusedIncreasingStateMachineState = (firstUnusedIncreasingStateMachineState != null) ? Math.Max(firstUnusedIncreasingStateMachineState.Value, maxState) : maxState; + firstUnusedIncreasingStateMachineState = (firstUnusedIncreasingStateMachineState != null) ? (StateMachineState)Math.Max((int)firstUnusedIncreasingStateMachineState.Value, (int)maxState) : maxState; if (minState < 0) { - firstUnusedDecreasingStateMachineState = (firstUnusedDecreasingStateMachineState != null) ? Math.Min(firstUnusedDecreasingStateMachineState.Value, minState) : minState; + firstUnusedDecreasingStateMachineState = (firstUnusedDecreasingStateMachineState != null) ? (StateMachineState)Math.Min((int)firstUnusedDecreasingStateMachineState.Value, (int)minState) : minState; } } } diff --git a/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs b/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs index d0bac38235b1f..557bffc19b857 100644 --- a/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs @@ -83,10 +83,10 @@ public abstract bool TryGetPreviousHoistedLocalSlotIndex( /// /// State number to be used for next state of the state machine, - /// or if none of the previous versions of the method was a state machine with a increasing state + /// or if none of the previous versions of the method was a state machine with an increasing state /// - /// True if the state number increases with progress, false if it decreases. - public abstract int? GetFirstUnusedStateMachineState(bool increasing); + /// True if the state number increases with progress, false if it decreases (e.g. states for iterator try-finally blocks, or iterator states of async iterators). + public abstract StateMachineState? GetFirstUnusedStateMachineState(bool increasing); /// /// For a given node associated with entering a state of a state machine in the new compilation, @@ -98,6 +98,6 @@ public abstract bool TryGetPreviousHoistedLocalSlotIndex( /// /// is an await expression, yield return statement, or try block syntax node. /// - public abstract bool TryGetPreviousStateMachineState(SyntaxNode syntax, out int stateOrdinal); + public abstract bool TryGetPreviousStateMachineState(SyntaxNode syntax, out StateMachineState state); } } diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index 889885970e3f9..9702f9e6408a3 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -738,6 +738,15 @@ public static int Count(this ImmutableArray items, Func predicate return count; } + public static int Sum(this ImmutableArray items, Func selector) + { + var sum = 0; + foreach (var item in items) + sum += selector(item); + + return sum; + } + internal static Dictionary> ToDictionary(this ImmutableArray items, Func keySelector, IEqualityComparer? comparer = null) where K : notnull { diff --git a/src/Compilers/Core/Portable/Compilation/CompilationOptions.cs b/src/Compilers/Core/Portable/Compilation/CompilationOptions.cs index 117354e214d75..dcc5aa05c87cf 100644 --- a/src/Compilers/Core/Portable/Compilation/CompilationOptions.cs +++ b/src/Compilers/Core/Portable/Compilation/CompilationOptions.cs @@ -261,6 +261,8 @@ protected set private readonly Lazy> _lazyErrors; + private int _hashCode; + // Expects correct arguments. internal CompilationOptions( OutputKind outputKind, @@ -651,7 +653,18 @@ protected bool EqualsHelper([NotNullWhen(true)] CompilationOptions? other) return equal; } - public abstract override int GetHashCode(); + public sealed override int GetHashCode() + { + if (_hashCode == 0) + { + var hashCode = ComputeHashCode(); + _hashCode = hashCode == 0 ? 1 : hashCode; + } + + return _hashCode; + } + + protected abstract int ComputeHashCode(); protected int GetHashCodeHelper() { diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index e26ebb058b82b..bd9687d74c9a2 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -691,7 +691,7 @@ protected bool ContainsTopLevelType(string fullEmittedName) static void AddTopLevelType(HashSet names, Cci.INamespaceTypeDefinition type) // _namesOfTopLevelTypes are only used to generated exported types, which are not emitted in EnC deltas (hence generation 0): - => names?.Add(MetadataHelpers.BuildQualifiedName(type.NamespaceName, Cci.MetadataWriter.GetMangledName(type, generation: 0))); + => names?.Add(MetadataHelpers.BuildQualifiedName(type.NamespaceName, Cci.MetadataWriter.GetMetadataName(type, generation: 0))); } public virtual ImmutableArray GetAdditionalTopLevelTypes() diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs index 5eb02ef5dfe3c..9ba047d15daff 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs @@ -167,9 +167,9 @@ protected abstract void GetStateMachineFieldMapFromMetadata( IReadOnlyDictionary? awaiterMap = null; IReadOnlyDictionary>? lambdaMap = null; IReadOnlyDictionary? closureMap = null; - IReadOnlyDictionary? stateMachineStateMap = null; - int? firstUnusedIncreasingStateMachineState = null; - int? firstUnusedDecreasingStateMachineState = null; + IReadOnlyDictionary? stateMachineStateMap = null; + StateMachineState? firstUnusedIncreasingStateMachineState = null; + StateMachineState? firstUnusedDecreasingStateMachineState = null; int hoistedLocalSlotCount = 0; int awaiterSlotCount = 0; @@ -376,7 +376,7 @@ private static void MakeLambdaAndClosureMaps( private static void MakeStateMachineStateMap( ImmutableArray debugInfos, - out IReadOnlyDictionary? map) + out IReadOnlyDictionary? map) { map = debugInfos.IsDefault ? null : diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs index 841bd83956ca3..d2761d6eb5344 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs @@ -35,9 +35,9 @@ internal sealed class EncVariableSlotAllocator : VariableSlotAllocator private readonly IReadOnlyDictionary? _hoistedLocalSlots; private readonly int _awaiterCount; private readonly IReadOnlyDictionary? _awaiterMap; - private readonly IReadOnlyDictionary? _stateMachineStateMap; // SyntaxOffset -> State Ordinal - private readonly int? _firstUnusedDecreasingStateMachineState; - private readonly int? _firstUnusedIncreasingStateMachineState; + private readonly IReadOnlyDictionary? _stateMachineStateMap; // SyntaxOffset -> State Ordinal + private readonly StateMachineState? _firstUnusedDecreasingStateMachineState; + private readonly StateMachineState? _firstUnusedIncreasingStateMachineState; // closures: private readonly IReadOnlyDictionary>? _lambdaMap; // SyntaxOffset -> (Lambda Id, Closure Ordinal) @@ -58,9 +58,9 @@ public EncVariableSlotAllocator( IReadOnlyDictionary? hoistedLocalSlots, int awaiterCount, IReadOnlyDictionary? awaiterMap, - IReadOnlyDictionary? stateMachineStateMap, - int? firstUnusedIncreasingStateMachineState, - int? firstUnusedDecreasingStateMachineState, + IReadOnlyDictionary? stateMachineStateMap, + StateMachineState? firstUnusedIncreasingStateMachineState, + StateMachineState? firstUnusedDecreasingStateMachineState, LambdaSyntaxFacts lambdaSyntaxFacts) { Debug.Assert(!previousLocals.IsDefault); @@ -329,19 +329,19 @@ public override bool TryGetPreviousLambda(SyntaxNode lambdaOrLambdaBodySyntax, b return false; } - public override int? GetFirstUnusedStateMachineState(bool increasing) + public override StateMachineState? GetFirstUnusedStateMachineState(bool increasing) => increasing ? _firstUnusedIncreasingStateMachineState : _firstUnusedDecreasingStateMachineState; - public override bool TryGetPreviousStateMachineState(SyntaxNode syntax, out int stateOrdinal) + public override bool TryGetPreviousStateMachineState(SyntaxNode syntax, out StateMachineState state) { if (_stateMachineStateMap != null && TryGetPreviousSyntaxOffset(syntax, out int syntaxOffset) && - _stateMachineStateMap.TryGetValue(syntaxOffset, out stateOrdinal)) + _stateMachineStateMap.TryGetValue(syntaxOffset, out state)) { return true; } - stateOrdinal = -1; + state = default; return false; } } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs b/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs index 797017298f9a0..0e78c4199d00c 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs @@ -49,7 +49,6 @@ internal EditAndContinueMethodDebugInformation( public static EditAndContinueMethodDebugInformation Create(ImmutableArray compressedSlotMap, ImmutableArray compressedLambdaMap) => Create(compressedSlotMap, compressedLambdaMap, compressedStateMachineStateMap: default); - /// /// Deserializes Edit and Continue method debug information from specified blobs. /// @@ -264,6 +263,9 @@ internal void SerializeLambdaMap(BlobBuilder writer) Debug.Assert(MethodOrdinal >= -1); writer.WriteCompressedInteger(MethodOrdinal + 1); + // Negative syntax offsets are rare - only when the syntax node is in an initializer of a field or property. + // To optimize for size calculate the base offset and adds it to all syntax offsets. In common cases (no negative offsets) + // this base offset will be 0. Otherwise it will be the lowest negative offset. int syntaxOffsetBaseline = -1; foreach (ClosureDebugInfo info in Closures) { @@ -329,7 +331,7 @@ private static unsafe ImmutableArray UncompressState int stateNumber = blobReader.ReadCompressedSignedInteger(); int syntaxOffset = syntaxOffsetBaseline + blobReader.ReadCompressedInteger(); - mapBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, stateNumber)); + mapBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, (StateMachineState)stateNumber)); count--; } } @@ -348,12 +350,15 @@ internal void SerializeStateMachineStates(BlobBuilder writer) writer.WriteCompressedInteger(StateMachineStates.Length); if (StateMachineStates.Length > 0) { + // Negative syntax offsets are rare - only when the syntax node is in an initializer of a field or property. + // To optimize for size calculate the base offset and adds it to all syntax offsets. In common cases (no negative offsets) + // this base offset will be 0. Otherwise it will be the lowest negative offset. int syntaxOffsetBaseline = Math.Min(StateMachineStates.Min(state => state.SyntaxOffset), 0); writer.WriteCompressedInteger(-syntaxOffsetBaseline); foreach (StateMachineStateDebugInfo state in StateMachineStates) { - writer.WriteCompressedSignedInteger(state.StateNumber); + writer.WriteCompressedSignedInteger((int)state.StateNumber); writer.WriteCompressedInteger(state.SyntaxOffset - syntaxOffsetBaseline); } } diff --git a/src/Compilers/Core/Portable/Emit/ErrorType.cs b/src/Compilers/Core/Portable/Emit/ErrorType.cs index 79a43e38fc187..83d61eb70c3c3 100644 --- a/src/Compilers/Core/Portable/Emit/ErrorType.cs +++ b/src/Compilers/Core/Portable/Emit/ErrorType.cs @@ -55,6 +55,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return null; + } + } +#nullable disable + bool Cci.ITypeReference.IsEnum { get diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs index c5bf550d98e2a..2ab12dba66c50 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs @@ -675,6 +675,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return UnderlyingNamedType.AssociatedFileIdentifier; + } + } +#nullable disable + string Cci.INamedEntity.Name { get diff --git a/src/Compilers/Core/Portable/InternalUtilities/Hash.cs b/src/Compilers/Core/Portable/InternalUtilities/Hash.cs index a1ea12a025329..f2d66d5918316 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/Hash.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/Hash.cs @@ -68,6 +68,25 @@ internal static int CombineValues(IEnumerable? values, int maxItemsToHash return hashCode; } + internal static int CombineValues(ImmutableDictionary values, int maxItemsToHash = int.MaxValue) + where TKey : notnull + { + if (values == null) + return 0; + + var hashCode = 0; + var count = 0; + foreach (var value in values) + { + if (count++ >= maxItemsToHash) + break; + + hashCode = Hash.Combine(value.GetHashCode(), hashCode); + } + + return hashCode; + } + internal static int CombineValues(T[]? values, int maxItemsToHash = int.MaxValue) { if (values == null) @@ -143,6 +162,25 @@ internal static int CombineValues(IEnumerable? values, StringComparer s return hashCode; } + internal static int CombineValues(ImmutableArray values, StringComparer stringComparer, int maxItemsToHash = int.MaxValue) + { + if (values == null) + return 0; + + var hashCode = 0; + var count = 0; + foreach (var value in values) + { + if (count++ >= maxItemsToHash) + break; + + if (value != null) + hashCode = Hash.Combine(stringComparer.GetHashCode(value), hashCode); + } + + return hashCode; + } + /// /// The offset bias value used in the FNV-1a algorithm /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function diff --git a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs index 03cfc874b2910..7a042ab03c067 100644 --- a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs +++ b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs @@ -470,10 +470,12 @@ internal static string GetAritySuffix(int arity) return (arity <= 9) ? s_aritySuffixesOneToNine[arity - 1] : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture)); } - internal static string ComposeAritySuffixedMetadataName(string name, int arity) +#nullable enable + internal static string ComposeAritySuffixedMetadataName(string name, int arity, string? associatedFileIdentifier) { - return arity == 0 ? name : name + GetAritySuffix(arity); + return associatedFileIdentifier + (arity == 0 ? name : name + GetAritySuffix(arity)); } +#nullable disable internal static int InferTypeArityFromMetadataName(string emittedTypeName) { diff --git a/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs b/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs index bcd9b1cc00509..e306f811888c4 100644 --- a/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs +++ b/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs @@ -45,7 +45,7 @@ public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespa else { int generation = (t is INamedTypeDefinition namedType) ? _writer.Module.GetTypeDefinitionGeneration(namedType) : 0; - typeName = MetadataWriter.GetMangledName((INamedTypeReference)t, generation); + typeName = MetadataWriter.GetMetadataName((INamedTypeReference)t, generation); INamespaceTypeDefinition namespaceTypeDef; if ((namespaceTypeDef = t.AsNamespaceTypeDefinition(_writer.Context)) != null) diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index 9b0743613eb5a..f952948663bbf 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -916,13 +916,13 @@ private static uint GetManagedResourceOffset(BlobBuilder resource, BlobBuilder r return (uint)result; } - public static string GetMangledName(INamedTypeReference namedType, int generation) + public static string GetMetadataName(INamedTypeReference namedType, int generation) { - string unmangledName = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; - - return namedType.MangleName - ? MetadataHelpers.ComposeAritySuffixedMetadataName(unmangledName, namedType.GenericParameterCount) - : unmangledName; + string nameWithGeneration = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; + string fileIdentifier = namedType.AssociatedFileIdentifier; + return namedType.MangleName || fileIdentifier != null + ? MetadataHelpers.ComposeAritySuffixedMetadataName(nameWithGeneration, namedType.GenericParameterCount, fileIdentifier) + : nameWithGeneration; } internal MemberReferenceHandle GetMemberReferenceHandle(ITypeMemberReference memberRef) @@ -2219,10 +2219,10 @@ private void PopulateExportedTypeTableRows() if ((namespaceTypeRef = exportedType.Type.AsNamespaceTypeReference) != null) { // exported types are not emitted in EnC deltas (hence generation 0): - string mangledTypeName = GetMangledName(namespaceTypeRef, generation: 0); + string metadataTypeName = GetMetadataName(namespaceTypeRef, generation: 0); - typeName = GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); - typeNamespace = GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); + typeName = GetStringHandleForNameAndCheckLength(metadataTypeName, namespaceTypeRef); + typeNamespace = GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, metadataTypeName); implementation = GetExportedTypeImplementation(namespaceTypeRef); attributes = exportedType.IsForwarder ? TypeAttributes.NotPublic | Constants.TypeAttributes_TypeForwarder : TypeAttributes.Public; } @@ -2231,9 +2231,9 @@ private void PopulateExportedTypeTableRows() Debug.Assert(exportedType.ParentIndex != -1); // exported types are not emitted in EnC deltas (hence generation 0): - string mangledTypeName = GetMangledName(nestedRef, generation: 0); + string metadataTypeName = GetMetadataName(nestedRef, generation: 0); - typeName = GetStringHandleForNameAndCheckLength(mangledTypeName, nestedRef); + typeName = GetStringHandleForNameAndCheckLength(metadataTypeName, nestedRef); typeNamespace = default(StringHandle); implementation = MetadataTokens.ExportedTypeHandle(exportedType.ParentIndex + 1); attributes = exportedType.IsForwarder ? TypeAttributes.NotPublic : TypeAttributes.NestedPublic; @@ -2710,13 +2710,13 @@ private void PopulateTypeDefTableRows() var moduleBuilder = Context.Module; int generation = moduleBuilder.GetTypeDefinitionGeneration(typeDef); - string mangledTypeName = GetMangledName(typeDef, generation); + string metadataTypeName = GetMetadataName(typeDef, generation); ITypeReference baseType = typeDef.GetBaseClass(Context); metadata.AddTypeDefinition( attributes: GetTypeAttributes(typeDef), - @namespace: (namespaceType != null) ? GetStringHandleForNamespaceAndCheckLength(namespaceType, mangledTypeName) : default(StringHandle), - name: GetStringHandleForNameAndCheckLength(mangledTypeName, typeDef), + @namespace: (namespaceType != null) ? GetStringHandleForNamespaceAndCheckLength(namespaceType, metadataTypeName) : default(StringHandle), + name: GetStringHandleForNameAndCheckLength(metadataTypeName, typeDef), baseType: (baseType != null) ? GetTypeHandle(baseType) : default(EntityHandle), fieldList: GetFirstFieldDefinitionHandle(typeDef), methodList: GetFirstMethodDefinitionHandle(typeDef)); @@ -2785,9 +2785,9 @@ private void PopulateTypeRefTableRows() // It's not possible to reference newer versions of reloadable types from another assembly, hence generation 0: // TODO: https://github.com/dotnet/roslyn/issues/54981 - string mangledTypeName = GetMangledName(nestedTypeRef, generation: 0); + string metadataTypeName = GetMetadataName(nestedTypeRef, generation: 0); - name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, nestedTypeRef); + name = this.GetStringHandleForNameAndCheckLength(metadataTypeName, nestedTypeRef); @namespace = default(StringHandle); } else @@ -2802,10 +2802,10 @@ private void PopulateTypeRefTableRows() // It's not possible to reference newer versions of reloadable types from another assembly, hence generation 0: // TODO: https://github.com/dotnet/roslyn/issues/54981 - string mangledTypeName = GetMangledName(namespaceTypeRef, generation: 0); + string metadataTypeName = GetMetadataName(namespaceTypeRef, generation: 0); - name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); - @namespace = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); + name = this.GetStringHandleForNameAndCheckLength(metadataTypeName, namespaceTypeRef); + @namespace = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, metadataTypeName); } metadata.AddTypeReference( diff --git a/src/Compilers/Core/Portable/PEWriter/RootModuleStaticConstructor.cs b/src/Compilers/Core/Portable/PEWriter/RootModuleStaticConstructor.cs index b6001e1a37a07..42c976c9a2d65 100644 --- a/src/Compilers/Core/Portable/PEWriter/RootModuleStaticConstructor.cs +++ b/src/Compilers/Core/Portable/PEWriter/RootModuleStaticConstructor.cs @@ -165,17 +165,16 @@ public RootModuleStaticConstructor(ITypeDefinition containingTypeDefinition, Imm public DynamicAnalysisMethodBodyData DynamicAnalysisData => null; - public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable; + throw ExceptionUtilities.Unreachable; } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable; + throw ExceptionUtilities.Unreachable; } } } diff --git a/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs b/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs index f90f9f573c9e6..dd6391debef0f 100644 --- a/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs +++ b/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs @@ -58,6 +58,11 @@ public bool MangleName get { return false; } } + public string? AssociatedFileIdentifier + { + get { return null; } + } + public string Name { get { return ""; } diff --git a/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs b/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs index 29115ffdff6a5..8a1639677e959 100644 --- a/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs +++ b/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using System.Text; using System.Diagnostics; +using System.Linq; namespace Microsoft.Cci { @@ -71,7 +72,7 @@ internal static string GetSerializedTypeName(this ITypeReference typeReference, sb.Append('.'); } - sb.Append(GetMangledAndEscapedName(namespaceType)); + sb.Append(GetEscapedMetadataName(namespaceType)); goto done; } @@ -113,7 +114,7 @@ internal static string GetSerializedTypeName(this ITypeReference typeReference, bool nestedTypeIsAssemblyQualified = false; sb.Append(GetSerializedTypeName(nestedType.GetContainingType(context), context, ref nestedTypeIsAssemblyQualified)); sb.Append('+'); - sb.Append(GetMangledAndEscapedName(nestedType)); + sb.Append(GetEscapedMetadataName(nestedType)); goto done; } @@ -193,12 +194,18 @@ private static void AppendAssemblyQualifierIfNecessary(StringBuilder sb, ITypeRe } } - private static string GetMangledAndEscapedName(INamedTypeReference namedType) + private static string GetEscapedMetadataName(INamedTypeReference namedType) { var pooled = PooledStringBuilder.GetInstance(); StringBuilder mangledName = pooled.Builder; const string needsEscaping = "\\[]*.+,& "; + if (namedType.AssociatedFileIdentifier is string fileIdentifier) + { + Debug.Assert(needsEscaping.All(c => !fileIdentifier.Contains(c))); + mangledName.Append(fileIdentifier); + } + foreach (var ch in namedType.Name) { if (needsEscaping.IndexOf(ch) >= 0) diff --git a/src/Compilers/Core/Portable/PEWriter/Types.cs b/src/Compilers/Core/Portable/PEWriter/Types.cs index 97e957df38f1c..2a4e50b50ac52 100644 --- a/src/Compilers/Core/Portable/PEWriter/Types.cs +++ b/src/Compilers/Core/Portable/PEWriter/Types.cs @@ -253,6 +253,9 @@ internal interface INamedTypeReference : ITypeReference, INamedEntity /// If true, the persisted type name is mangled by appending "`n" where n is the number of type parameters, if the number of type parameters is greater than 0. /// bool MangleName { get; } + + /// Indicates that the type is scoped to the file it is declared in. Used as a prefix for the metadata name. + string? AssociatedFileIdentifier { get; } } /// diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index bf7b9db988916..3fd40518ba6c6 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ *REMOVED*override abstract Microsoft.CodeAnalysis.Diagnostic.Equals(object? obj) -> bool +*REMOVED*override abstract Microsoft.CodeAnalysis.CompilationOptions.GetHashCode() -> int abstract Microsoft.CodeAnalysis.SymbolVisitor.DefaultResult.get -> TResult Microsoft.CodeAnalysis.Compilation.GetTypesByMetadataName(string! fullyQualifiedMetadataName) -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.GeneratorAttributeSyntaxContext @@ -71,6 +72,7 @@ Microsoft.CodeAnalysis.ISymbol.Accept(Microsoft.CodeAnalysis Microsoft.CodeAnalysis.SymbolVisitor Microsoft.CodeAnalysis.SymbolVisitor.SymbolVisitor() -> void Microsoft.CodeAnalysis.SyntaxValueProvider.ForAttributeWithMetadataName(string! fullyQualifiedMetadataName, System.Func! predicate, System.Func! transform) -> Microsoft.CodeAnalysis.IncrementalValuesProvider +override sealed Microsoft.CodeAnalysis.CompilationOptions.GetHashCode() -> int override sealed Microsoft.CodeAnalysis.Diagnostic.Equals(object? obj) -> bool *REMOVED*static Microsoft.CodeAnalysis.SyntaxNodeExtensions.ReplaceSyntax(this TRoot! root, System.Collections.Generic.IEnumerable! nodes, System.Func! computeReplacementNode, System.Collections.Generic.IEnumerable! tokens, System.Func! computeReplacementToken, System.Collections.Generic.IEnumerable! trivia, System.Func! computeReplacementTrivia) -> TRoot! static Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation.Create(System.Collections.Immutable.ImmutableArray compressedSlotMap, System.Collections.Immutable.ImmutableArray compressedLambdaMap, System.Collections.Immutable.ImmutableArray compressedStateMachineStateMap) -> Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation @@ -111,4 +113,5 @@ virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitPointerTyp virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitProperty(Microsoft.CodeAnalysis.IPropertySymbol! symbol, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitRangeVariable(Microsoft.CodeAnalysis.IRangeVariableSymbol! symbol, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitTypeParameter(Microsoft.CodeAnalysis.ITypeParameterSymbol! symbol, TArgument argument) -> TResult -Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.UnsignedRightShift = 25 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind \ No newline at end of file +Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.UnsignedRightShift = 25 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind +Microsoft.CodeAnalysis.INamedTypeSymbol.IsFile.get -> bool \ No newline at end of file diff --git a/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs b/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs index e505d313ada91..6eac7e1ad1f3f 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.SourceGeneration internal interface ISyntaxHelper { bool IsCaseSensitive { get; } + int AttributeListKind { get; } bool IsValidIdentifier(string name); @@ -33,13 +34,14 @@ internal interface ISyntaxHelper /// /// must be a compilation unit or namespace block. /// - void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); + void AddAliases(GreenNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); void AddAliases(CompilationOptions options, ArrayBuilder<(string aliasName, string symbolName)> aliases); } internal abstract class AbstractSyntaxHelper : ISyntaxHelper { public abstract bool IsCaseSensitive { get; } + public abstract int AttributeListKind { get; } public abstract bool IsValidIdentifier(string name); @@ -56,7 +58,7 @@ internal abstract class AbstractSyntaxHelper : ISyntaxHelper public abstract bool IsLambdaExpression(SyntaxNode node); - public abstract void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); + public abstract void AddAliases(GreenNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); public abstract void AddAliases(CompilationOptions options, ArrayBuilder<(string aliasName, string symbolName)> aliases); } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/BatchNode.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/BatchNode.cs index 0b2604db436e3..1e5b1a60045a5 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/BatchNode.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/BatchNode.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. -using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -30,6 +28,81 @@ public BatchNode(IIncrementalGeneratorNode sourceNode, IEqualityComparer public IIncrementalGeneratorNode> WithTrackingName(string name) => new BatchNode(_sourceNode, _comparer, name); + private (ImmutableArray, ImmutableArray<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>) GetValuesAndInputs( + NodeStateTable sourceTable, + NodeStateTable> previousTable, + NodeStateTable>.Builder newTable) + { + // Do an initial pass to both get the steps, and determine how many entries we'll have. + var sourceInputsBuilder = newTable.TrackIncrementalSteps ? ArrayBuilder<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>.GetInstance() : null; + + var entryCount = 0; + foreach (var entry in sourceTable) + { + // Always keep track of its step information, regardless of if the entry was removed or not, so we + // can accurately report how long it took and what actually happened (for testing validation). + sourceInputsBuilder?.Add((entry.Step!, entry.OutputIndex)); + + if (entry.State != EntryState.Removed) + entryCount++; + } + + var sourceInputs = sourceInputsBuilder != null ? sourceInputsBuilder.ToImmutableAndFree() : default; + + // First, see if we can reuse the entries from previousTable. + // If not, produce the actual values we need from sourceTable. + var result = tryReusePreviousTableValues(entryCount) ?? computeCurrentTableValues(entryCount); + return (result, sourceInputs); + + ImmutableArray? tryReusePreviousTableValues(int entryCount) + { + if (previousTable.Count != 1) + return null; + + var previousItems = previousTable.Single().item; + + // If they don't have the same length, we clearly can't reuse them. + if (previousItems.Length != entryCount) + return null; + + var indexInPrevious = 0; + foreach (var entry in sourceTable) + { + if (entry.State == EntryState.Removed) + continue; + + // If the entries aren't the same, we can't reuse. + if (!EqualityComparer.Default.Equals(entry.Item, previousItems[indexInPrevious])) + return null; + + indexInPrevious++; + } + + // We better have the exact same count as previousItems as we checked that above. + Debug.Assert(indexInPrevious == previousItems.Length); + + // Looks good, we can reuse this. + return previousItems; + } + + ImmutableArray computeCurrentTableValues(int entryCount) + { + // Important: we initialize with the exact capacity we need here so that we don't make a pointless + // scratch array that may be very large and may cause GC churn when it cannot be returned to the pool. + var builder = ArrayBuilder.GetInstance(entryCount); + foreach (var entry in sourceTable) + { + if (entry.State == EntryState.Removed) + continue; + + builder.Add(entry.Item); + } + + Debug.Assert(builder.Count == entryCount); + return builder.ToImmutableAndFree(); + } + } + public NodeStateTable> UpdateStateTable(DriverStateTable.Builder builder, NodeStateTable> previousTable, CancellationToken cancellationToken) { // grab the source inputs @@ -43,29 +116,14 @@ public NodeStateTable> UpdateStateTable(DriverStateTable. // - Modified otherwise // update the table - var newTable = builder.CreateTableBuilder(previousTable, _name); + var newTable = builder.CreateTableBuilder(previousTable, _name, _comparer); // If this execution is tracking steps, then the source table should have also tracked steps or be the empty table. Debug.Assert(!newTable.TrackIncrementalSteps || (sourceTable.HasTrackedSteps || sourceTable.IsEmpty)); var stopwatch = SharedStopwatch.StartNew(); - var sourceValuesBuilder = ArrayBuilder.GetInstance(); - var sourceInputsBuilder = newTable.TrackIncrementalSteps ? ArrayBuilder<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>.GetInstance() : null; - - foreach (var entry in sourceTable) - { - // At this point, we can remove any 'Removed' items and ensure they're not in our list of states. - if (entry.State != EntryState.Removed) - sourceValuesBuilder.Add(entry.Item); - - // However, regardless of if the entry was removed or not, we still keep track of its step information - // so we can accurately report how long it took and what actually happened (for testing validation). - sourceInputsBuilder?.Add((entry.Step!, entry.OutputIndex)); - } - - var sourceValues = sourceValuesBuilder.ToImmutableAndFree(); - var sourceInputs = newTable.TrackIncrementalSteps ? sourceInputsBuilder!.ToImmutableAndFree() : default; + var (sourceValues, sourceInputs) = GetValuesAndInputs(sourceTable, previousTable, newTable); if (previousTable.IsEmpty) { diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/CombineNode.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/CombineNode.cs index aef82e0a84744..732f6708669b3 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/CombineNode.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/CombineNode.cs @@ -41,7 +41,8 @@ public CombineNode(IIncrementalGeneratorNode input1, IIncrementalGenera return previousTable; } - var builder = graphState.CreateTableBuilder(previousTable, _name); + var totalEntryItemCount = input1Table.GetTotalEntryItemCount(); + var builder = graphState.CreateTableBuilder(previousTable, _name, _comparer, totalEntryItemCount); // Semantics of a join: // @@ -75,13 +76,14 @@ public CombineNode(IIncrementalGeneratorNode input1, IIncrementalGenera } } + Debug.Assert(builder.Count == totalEntryItemCount); return builder.ToImmutableAndFree(); } private NodeStateTable<(TInput1, TInput2)> RecordStepsForCachedTable(DriverStateTable.Builder graphState, NodeStateTable<(TInput1, TInput2)> previousTable, NodeStateTable input1Table, NodeStateTable input2Table) { Debug.Assert(input1Table.HasTrackedSteps && input2Table.IsCached); - var builder = graphState.CreateTableBuilder(previousTable, _name); + var builder = graphState.CreateTableBuilder(previousTable, _name, _comparer); (_, IncrementalGeneratorRunStep? input2Step) = input2Table.Single(); foreach (var entry in input1Table) { diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/DriverStateTable.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/DriverStateTable.cs index edf3b8a681ef8..e465690fcbda8 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/DriverStateTable.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/DriverStateTable.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -61,9 +62,10 @@ public NodeStateTable GetLatestStateTableForNode(IIncrementalGeneratorNode return newTable; } - public NodeStateTable.Builder CreateTableBuilder(NodeStateTable previousTable, string? stepName) + public NodeStateTable.Builder CreateTableBuilder( + NodeStateTable previousTable, string? stepName, IEqualityComparer? equalityComparer, int? tableCapacity = null) { - return previousTable.ToBuilder(stepName, DriverState.TrackIncrementalSteps); + return previousTable.ToBuilder(stepName, DriverState.TrackIncrementalSteps, equalityComparer, tableCapacity); } public DriverStateTable ToImmutable() diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs index 96af759a55bb0..8d7b13f033bec 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs @@ -52,7 +52,7 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder graphState, N Debug.Assert(added); } - var builder = graphState.CreateTableBuilder(previousTable, _name); + var builder = graphState.CreateTableBuilder(previousTable, _name, _comparer); // We always have no inputs steps into an InputNode, but we track the difference between "no inputs" (empty collection) and "no step information" (default value) var noInputStepsStepInfo = builder.TrackIncrementalSteps ? ImmutableArray<(IncrementalGeneratorRunStep, int)>.Empty : default; diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs index e8ca334d762db..c27af17f5ef17 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs @@ -75,7 +75,7 @@ private NodeStateTable(ImmutableArray states, ImmutableArray _states.Length; } + public int Count => _states.Length; /// /// Indicates if every entry in this table has a state of @@ -88,6 +88,9 @@ private NodeStateTable(ImmutableArray states, ImmutableArray Steps { get; } + public int GetTotalEntryItemCount() + => _states.Sum(static e => e.Count); + public IEnumerator> GetEnumerator() { for (int i = 0; i < _states.Length; i++) @@ -106,16 +109,20 @@ public NodeStateTable AsCached() if (IsCached) return this; - var compacted = ArrayBuilder.GetInstance(); + var nonRemovedCount = _states.Count(static e => !e.IsRemoved); + + var compacted = ArrayBuilder.GetInstance(nonRemovedCount); foreach (var entry in _states) { if (!entry.IsRemoved) - { compacted.Add(entry.AsCached()); - } } + // When we're preparing a table for caching between runs, we drop the step information as we cannot guarantee the graph structure while also updating // the input states + + // Ensure we are completely full so that ToImmutable translates to a MoveToImmutable + Debug.Assert(compacted.Count == nonRemovedCount); return new NodeStateTable(compacted.ToImmutableAndFree(), ImmutableArray.Empty, hasTrackedSteps: false); } @@ -127,19 +134,20 @@ public NodeStateTable AsCached() return (_states[^1].GetItem(0), HasTrackedSteps ? Steps[^1] : null); } - public Builder ToBuilder(string? stepName, bool stepTrackingEnabled) - => new Builder(this, stepName, stepTrackingEnabled); + public Builder ToBuilder(string? stepName, bool stepTrackingEnabled, IEqualityComparer? equalityComparer = null, int? tableCapacity = null) + => new(this, stepName, stepTrackingEnabled, equalityComparer, tableCapacity); - public NodeStateTable CreateCachedTableWithUpdatedSteps(NodeStateTable inputTable, string? stepName) + public NodeStateTable CreateCachedTableWithUpdatedSteps(NodeStateTable inputTable, string? stepName, IEqualityComparer equalityComparer) { Debug.Assert(inputTable.HasTrackedSteps && inputTable.IsCached); - NodeStateTable.Builder builder = ToBuilder(stepName, stepTrackingEnabled: true); + NodeStateTable.Builder builder = ToBuilder(stepName, stepTrackingEnabled: true, equalityComparer); foreach (var entry in inputTable) { var inputs = ImmutableArray.Create((entry.Step!, entry.OutputIndex)); bool usedCachedEntry = builder.TryUseCachedEntries(TimeSpan.Zero, inputs); Debug.Assert(usedCachedEntry); } + return builder.ToImmutableAndFree(); } @@ -149,22 +157,40 @@ public sealed class Builder private readonly NodeStateTable _previous; private readonly string? _name; + private readonly IEqualityComparer _equalityComparer; private readonly ArrayBuilder? _steps; [MemberNotNullWhen(true, nameof(_steps))] public bool TrackIncrementalSteps => _steps is not null; - internal Builder(NodeStateTable previous, string? name, bool stepTrackingEnabled) +#if DEBUG + private readonly int? _requestedTableCapacity; +#endif + + internal Builder( + NodeStateTable previous, + string? name, + bool stepTrackingEnabled, + IEqualityComparer? equalityComparer, + int? tableCapacity) { - _states = ArrayBuilder.GetInstance(previous._states.Length); +#if DEBUG + _requestedTableCapacity = tableCapacity; +#endif + // If the caller specified a desired capacity, then use that. Otherwise, use the previous table's total + // entry count as a reasonable approximation for what we will need. + _states = ArrayBuilder.GetInstance(tableCapacity ?? previous.GetTotalEntryItemCount()); _previous = previous; _name = name; + _equalityComparer = equalityComparer ?? EqualityComparer.Default; if (stepTrackingEnabled) { _steps = ArrayBuilder.GetInstance(); } } + public int Count => _states.Count; + public bool TryRemoveEntries(TimeSpan elapsedTime, ImmutableArray<(IncrementalGeneratorRunStep InputStep, int OutputIndex)> stepInputs) { if (_previous._states.Length <= _states.Count) @@ -229,7 +255,7 @@ public bool TryModifyEntry(T value, IEqualityComparer comparer, TimeSpan elap } Debug.Assert(_previous._states[_states.Count].Count == 1); - var (chosen, state) = GetModifiedItemAndState(_previous._states[_states.Count].GetItem(0), value, comparer); + var (chosen, state, _) = GetModifiedItemAndState(_previous._states[_states.Count].GetItem(0), value, comparer); _states.Add(new TableEntry(chosen, state)); RecordStepInfoForLastEntry(elapsedTime, stepInputs, overallInputState); return true; @@ -259,35 +285,72 @@ public bool TryModifyEntries(ImmutableArray outputs, IEqualityComparer com { RecordStepInfoForLastEntry(elapsedTime, stepInputs, EntryState.Cached); } + return true; } - var modified = new TableEntry.Builder(); + // We may be able to move the previous entry over wholesale. So avoid creating an builder and doing any + // expensive work there until necessary (e.g. we detected either a different item or a different state). + // We can only do this if the counts of before/after are the same. If not, then obviously something + // changed and we can't reuse the before item. + + var totalBuilderItems = Math.Max(previousEntry.Count, outputs.Length); + var builder = previousEntry.Count == outputs.Length ? null : new TableEntry.Builder(capacity: totalBuilderItems); + var sharedCount = Math.Min(previousEntry.Count, outputs.Length); // cached or modified items for (int i = 0; i < sharedCount; i++) { - var previous = previousEntry.GetItem(i); - var replacement = outputs[i]; + var previousItem = previousEntry.GetItem(i); + var previousState = previousEntry.GetState(i); + var replacementItem = outputs[i]; + + var (chosenItem, state, chosePrevious) = GetModifiedItemAndState(previousItem, replacementItem, comparer); + + if (builder != null) + { + // if we have a builder, then we're keeping track of all entries no matter what. + builder.Add(chosenItem, state); + continue; + } + + if (!chosePrevious || state != previousState) + { + // We don't have a builder, but we also can't use the previous entry. Make a builder, copy + // everything prior to this point to it, and then add the latest entry. + builder = new TableEntry.Builder(capacity: totalBuilderItems); + for (int j = 0; j < i; j++) + builder.Add(previousEntry.GetItem(j), previousEntry.GetState(j)); + + builder.Add(chosenItem, state); + continue; + } - (var chosen, var state) = GetModifiedItemAndState(previous, replacement, comparer); - modified.Add(chosen, state); + // otherwise, we don't have a builder and we are still able to use the previous entry. Keep going + // without constructing anything. } // removed for (int i = sharedCount; i < previousEntry.Count; i++) { - modified.Add(previousEntry.GetItem(i), EntryState.Removed); + // We know we must have a builder because we only get into this path when the counts are different + // (and thus we created a builder at the start). + builder!.Add(previousEntry.GetItem(i), EntryState.Removed); } // added for (int i = sharedCount; i < outputs.Length; i++) { - modified.Add(outputs[i], EntryState.Added); + // We know we must have a builder because we only get into this path when the counts are different + // (and thus we created a builder at the start). + builder!.Add(outputs[i], EntryState.Added); } - _states.Add(modified.ToImmutableAndFree()); + // If we still don't have a builder, then we can reuse the previous table entry entirely. Otherwise, + // construct the new one from the values collected. + _states.Add(builder == null ? previousEntry : builder.ToImmutableAndFree()); + RecordStepInfoForLastEntry(elapsedTime, stepInputs, overallInputState); return true; } @@ -359,19 +422,39 @@ public NodeStateTable ToImmutableAndFree() return NodeStateTable.Empty; } +#if DEBUG + // If the caller requested a specific capacity, then we should have added exactly that amount of items. + Debug.Assert(_requestedTableCapacity == null || _requestedTableCapacity == _states.Count); +#endif + + // if we added the exact same entries as before, then we can directly embed previous' entry array, + // avoiding a costly allocation of the same data. + ImmutableArray finalStates; + if (_states.Count == _previous.Count && _states.SequenceEqual(_previous._states, (e1, e2) => e1.Matches(e2, _equalityComparer))) + { + finalStates = _previous._states; + _states.Free(); + } + else + { + // Important to use ToImmutableAndFree so that we will MoveToImmutable when the requested capacity + // equals the count. + finalStates = _states.ToImmutableAndFree(); + } + return new NodeStateTable( - _states.ToImmutableAndFree(), + finalStates, TrackIncrementalSteps ? _steps.ToImmutableAndFree() : default, hasTrackedSteps: TrackIncrementalSteps); } - private static (T chosen, EntryState state) GetModifiedItemAndState(T previous, T replacement, IEqualityComparer comparer) + private static (T chosen, EntryState state, bool chosePrevious) GetModifiedItemAndState(T previous, T replacement, IEqualityComparer comparer) { // when comparing an item to check if its modified we explicitly cache the *previous* item in the case where its // considered to be equal. This ensures that subsequent comparisons are stable across future generation passes. return comparer.Equals(previous, replacement) - ? (previous, EntryState.Cached) - : (replacement, EntryState.Modified); + ? (previous, EntryState.Cached, chosePrevious: true) + : (replacement, EntryState.Modified, chosePrevious: false); } } @@ -407,6 +490,23 @@ private TableEntry(T? item, ImmutableArray items, ImmutableArray this._states = states; } + public bool Matches(TableEntry entry, IEqualityComparer equalityComparer) + { + if (!_states.SequenceEqual(entry._states)) + return false; + + if (this.Count != entry.Count) + return false; + + for (int i = 0, n = this.Count; i < n; i++) + { + if (!equalityComparer.Equals(this.GetItem(i), entry.GetItem(i))) + return false; + } + + return true; + } + public bool IsCached => this._states == s_allCachedEntries || this._states.All(s => s == EntryState.Cached); public bool IsRemoved => this._states == s_allRemovedEntries || this._states.All(s => s == EntryState.Removed); @@ -479,12 +579,14 @@ public override string ToString() { sb.Builder.Append(','); } + sb.Builder.Append(" ("); sb.Builder.Append(GetItem(i)); sb.Builder.Append(':'); sb.Builder.Append(GetState(i)); sb.Builder.Append(')'); } + sb.Builder.Append(" }"); return sb.ToStringAndFree(); } @@ -493,12 +595,19 @@ public override string ToString() public sealed class Builder { - private readonly ArrayBuilder _items = ArrayBuilder.GetInstance(); + private readonly ArrayBuilder _items; private ArrayBuilder? _states; - private EntryState? _currentState; + private readonly int _requestedCapacity; + + public Builder(int capacity) + { + _items = ArrayBuilder.GetInstance(capacity); + _requestedCapacity = capacity; + } + public void Add(T item, EntryState state) { _items.Add(item); @@ -512,7 +621,13 @@ public void Add(T item, EntryState state) } else if (_currentState != state) { - _states = ArrayBuilder.GetInstance(_items.Count - 1, _currentState.Value); + // Create a builder with the right capacity (so we don't waste scratch space). Copy all the same + // prior values all the way up to the last item we're about to add. + _states = ArrayBuilder.GetInstance(_requestedCapacity); + for (int i = 0, n = _items.Count - 1; i < n; i++) + _states.Add(_currentState.Value); + + // then finally add the new value at the end. _states.Add(state); } } @@ -520,7 +635,10 @@ public void Add(T item, EntryState state) public TableEntry ToImmutableAndFree() { Debug.Assert(_currentState.HasValue, "Created a builder with no values?"); - int numItems = _items.Count; + + Debug.Assert(_items.Count == _requestedCapacity); + Debug.Assert(_states == null || _states.Count == _requestedCapacity); + return new TableEntry(item: default, _items.ToImmutableAndFree(), _states?.ToImmutableAndFree() ?? GetSingleArray(_currentState.Value)); } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs index 7f5aab8e66ff4..53194bc80c94a 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs @@ -49,7 +49,7 @@ public Builder(PredicateSyntaxStrategy owner, object key, StateTableStore tab _comparer = comparer; _key = key; _filterTable = table.GetStateTableOrEmpty(_owner._filterKey).ToBuilder(stepName: null, trackIncrementalSteps); - _transformTable = table.GetStateTableOrEmpty(_key).ToBuilder(_name, trackIncrementalSteps); + _transformTable = table.GetStateTableOrEmpty(_key).ToBuilder(_name, trackIncrementalSteps, _comparer); } public void SaveStateAndFree(StateTableStore.Builder tables) diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs index b6b715f807af5..805b2494a179f 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs @@ -43,12 +43,12 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder graphSt { if (graphState.DriverState.TrackIncrementalSteps) { - return previousTable.CreateCachedTableWithUpdatedSteps(sourceTable, stepName); + return previousTable.CreateCachedTableWithUpdatedSteps(sourceTable, stepName, EqualityComparer.Default); } return previousTable; } - var nodeTable = graphState.CreateTableBuilder(previousTable, stepName); + var nodeTable = graphState.CreateTableBuilder(previousTable, stepName, EqualityComparer.Default); foreach (var entry in sourceTable) { var inputs = nodeTable.TrackIncrementalSteps ? ImmutableArray.Create((entry.Step!, entry.OutputIndex)) : default; diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 9c3b9dad0afc9..b7050e3ccda71 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -104,7 +104,7 @@ static GlobalAliases getGlobalAliasesInCompilationUnit( Debug.Assert(compilationUnit is ICompilationUnitSyntax); var globalAliases = Aliases.GetInstance(); - syntaxHelper.AddAliases(compilationUnit, globalAliases, global: true); + syntaxHelper.AddAliases(compilationUnit.Green, globalAliases, global: true); return GlobalAliases.Create(globalAliases.ToImmutableAndFree()); } @@ -119,9 +119,12 @@ private static ImmutableArray GetMatchingNodes( CancellationToken cancellationToken) { var compilationUnit = syntaxTree.GetRoot(cancellationToken); - Debug.Assert(compilationUnit is ICompilationUnitSyntax); + // Walk the green node tree first to avoid allocating the entire red tree for files that have no attributes. + if (!ContainsAttributeList(compilationUnit.Green, syntaxHelper.AttributeListKind)) + return ImmutableArray.Empty; + var isCaseSensitive = syntaxHelper.IsCaseSensitive; var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; @@ -158,14 +161,14 @@ void recurse(SyntaxNode node) if (node is ICompilationUnitSyntax) { - syntaxHelper.AddAliases(node, localAliases, global: false); + syntaxHelper.AddAliases(node.Green, localAliases, global: false); recurseChildren(node); } else if (syntaxHelper.IsAnyNamespaceBlock(node)) { var localAliasCount = localAliases.Count; - syntaxHelper.AddAliases(node, localAliases, global: false); + syntaxHelper.AddAliases(node.Green, localAliases, global: false); recurseChildren(node); @@ -285,4 +288,21 @@ bool matchesAttributeName(string currentAttributeName, bool withAttributeSuffix) return false; } } + + private static bool ContainsAttributeList(GreenNode node, int attributeListKind) + { + if (node.RawKind == attributeListKind) + return true; + + foreach (var child in node.ChildNodesAndTokens()) + { + if (node.IsToken) + return false; + + if (ContainsAttributeList(child, attributeListKind)) + return true; + } + + return false; + } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/TransformNode.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/TransformNode.cs index c0bfdfe19b624..6493b3bc9673a 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/TransformNode.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/TransformNode.cs @@ -45,7 +45,7 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder builder { if (builder.DriverState.TrackIncrementalSteps) { - return previousTable.CreateCachedTableWithUpdatedSteps(sourceTable, _name); + return previousTable.CreateCachedTableWithUpdatedSteps(sourceTable, _name, _comparer); } return previousTable; } @@ -56,7 +56,8 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder builder // - Added: perform transform and add // - Modified: perform transform and do element wise comparison with previous results - var newTable = builder.CreateTableBuilder(previousTable, _name); + var totalEntryItemCount = sourceTable.GetTotalEntryItemCount(); + var newTable = builder.CreateTableBuilder(previousTable, _name, _comparer, totalEntryItemCount); foreach (var entry in sourceTable) { @@ -77,6 +78,8 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder builder } } } + + Debug.Assert(newTable.Count == totalEntryItemCount); return newTable.ToImmutableAndFree(); } diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs index 14346d9edbf25..d5b83d44d8fbc 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs @@ -72,5 +72,10 @@ internal enum SymbolDisplayCompilerInternalOptions /// Includes the scoped keyword. /// IncludeScoped = 1 << 9, + + /// + /// Display `MyType@File.cs` instead of `MyType`. + /// + IncludeContainingFileForFileTypes = 1 << 10, } } diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs index d8a5dc6aba65e..20f8c6d92d74c 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs @@ -202,7 +202,8 @@ public class SymbolDisplayFormat SymbolDisplayCompilerInternalOptions.IncludeScriptType | SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames | SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes | - SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers); + SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers | + SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes); /// /// A verbose format for displaying symbols (useful for testing). diff --git a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs index 3ba82acb88738..f8fab6b35decf 100644 --- a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs @@ -56,6 +56,11 @@ public interface INamedTypeSymbol : ITypeSymbol /// bool IsComImport { get; } + /// + /// Indicates the type is declared in source and is only visible in the file it is declared in. + /// + bool IsFile { get; } + /// /// Returns collection of names of members declared within this type. /// diff --git a/src/Compilers/Core/RebuildTest/BasicDeterministicKeyBuilderTests.cs b/src/Compilers/Core/RebuildTest/BasicDeterministicKeyBuilderTests.cs index 887a382cc2d3d..77b719b1c0abe 100644 --- a/src/Compilers/Core/RebuildTest/BasicDeterministicKeyBuilderTests.cs +++ b/src/Compilers/Core/RebuildTest/BasicDeterministicKeyBuilderTests.cs @@ -57,7 +57,7 @@ public void VerifyUpToDate() { verifyCount(11); verifyCount(10); - verifyCount(62); + verifyCount(63); verifyCount(22); static void verifyCount(int expected) diff --git a/src/Compilers/Core/RebuildTest/CSharpDeterministicKeyBuilderTests.cs b/src/Compilers/Core/RebuildTest/CSharpDeterministicKeyBuilderTests.cs index 59a433c74987d..56bbfeda63996 100644 --- a/src/Compilers/Core/RebuildTest/CSharpDeterministicKeyBuilderTests.cs +++ b/src/Compilers/Core/RebuildTest/CSharpDeterministicKeyBuilderTests.cs @@ -62,7 +62,7 @@ public void VerifyUpToDate() { verifyCount(11); verifyCount(10); - verifyCount(62); + verifyCount(63); verifyCount(9); static void verifyCount(int expected) diff --git a/src/Compilers/Server/VBCSCompilerTests/VBCSCompiler.UnitTests.csproj b/src/Compilers/Server/VBCSCompilerTests/VBCSCompiler.UnitTests.csproj index fd0181fddd306..8ee6e9160de67 100644 --- a/src/Compilers/Server/VBCSCompilerTests/VBCSCompiler.UnitTests.csproj +++ b/src/Compilers/Server/VBCSCompilerTests/VBCSCompiler.UnitTests.csproj @@ -7,7 +7,7 @@ true net6.0;net472 - @@ -33,6 +33,7 @@ + diff --git a/src/Compilers/Test/Core/Assert/AssertEx.cs b/src/Compilers/Test/Core/Assert/AssertEx.cs index 7cdcdded82c88..66a4af48e0267 100644 --- a/src/Compilers/Test/Core/Assert/AssertEx.cs +++ b/src/Compilers/Test/Core/Assert/AssertEx.cs @@ -555,7 +555,7 @@ public static void AssertEqualToleratingWhitespaceDifferences( string expected, string actual, string message = null, - bool escapeQuotes = true, + bool escapeQuotes = false, [CallerFilePath] string expectedValueSourcePath = null, [CallerLineNumber] int expectedValueSourceLine = 0) { diff --git a/src/Compilers/Test/Core/Compilation/CompilationDifference.cs b/src/Compilers/Test/Core/Compilation/CompilationDifference.cs index 1c2ba92547a86..f47c3972f4c15 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationDifference.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationDifference.cs @@ -62,7 +62,7 @@ public void VerifyIL( [CallerFilePath] string callerPath = null) { string actualIL = ILDelta.GetMethodIL(); - AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: true, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine); + AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: false, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine); } public void VerifyLocalSignature( @@ -96,7 +96,7 @@ internal void VerifyIL( } string actualIL = ILBuilderVisualizer.ILBuilderToString(ilBuilder, mapLocal ?? ToLocalInfo, sequencePointMarkers); - AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: true, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine); + AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: false, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine); } internal string GetMethodIL(string qualifiedMethodName) diff --git a/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs b/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs index 0a7d4b5916ba8..f814fe58625cc 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs @@ -37,7 +37,7 @@ internal static void VerifyIL( expectedIL = expectedIL.Replace(moduleNamePlaceholder, moduleName); } - AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: true, expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine); + AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: false, expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine); } internal static ImmutableArray> GetExplicitlyDeclaredMethods(this CompilationTestData data) diff --git a/src/Compilers/Test/Core/Compilation/RuntimeUtilities.cs b/src/Compilers/Test/Core/Compilation/RuntimeUtilities.cs index eb0da6d988aec..1af25509283b0 100644 --- a/src/Compilers/Test/Core/Compilation/RuntimeUtilities.cs +++ b/src/Compilers/Test/Core/Compilation/RuntimeUtilities.cs @@ -27,6 +27,9 @@ public static partial class RuntimeUtilities #endif internal static bool IsCoreClrRuntime => !IsDesktopRuntime; + internal static bool IsCoreClr6Runtime + => IsCoreClrRuntime && RuntimeInformation.FrameworkDescription.StartsWith(".NET 6.", StringComparison.Ordinal); + internal static BuildPaths CreateBuildPaths(string workingDirectory, string sdkDirectory = null, string tempDirectory = null) { tempDirectory ??= Path.GetTempPath(); diff --git a/src/Compilers/Test/Core/CompilationVerifier.cs b/src/Compilers/Test/Core/CompilationVerifier.cs index d22002f2ab12d..82a5f9c2209b5 100644 --- a/src/Compilers/Test/Core/CompilationVerifier.cs +++ b/src/Compilers/Test/Core/CompilationVerifier.cs @@ -435,7 +435,7 @@ public CompilationVerifier VerifyIL( [CallerLineNumber] int callerLine = 0, string source = null) { - return VerifyILImpl(qualifiedMethodName, expectedIL, realIL, sequencePoints, callerPath, callerLine, escapeQuotes: true, source: source); + return VerifyILImpl(qualifiedMethodName, expectedIL, realIL, sequencePoints, callerPath, callerLine, escapeQuotes: false, source: source); } public void VerifyILMultiple(params string[] qualifiedMethodNamesAndExpectedIL) diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index 88fd8881b014f..d3a04699d4697 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -291,6 +291,7 @@ public static class Features public const string RemoveUnnecessaryLineContinuation = nameof(RemoveUnnecessaryLineContinuation); public const string Rename = nameof(Rename); public const string RenameTracking = nameof(RenameTracking); + public const string RoslynLSPSnippetConverter = nameof(RoslynLSPSnippetConverter); public const string SignatureHelp = nameof(SignatureHelp); public const string Simplification = nameof(Simplification); public const string SmartIndent = nameof(SmartIndent); diff --git a/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb b/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb index 22f8d32a16009..0ea8e1ba9b275 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb @@ -375,7 +375,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If rightPart.Kind = SyntaxKind.GenericName Then arity = DirectCast(rightPart, GenericNameSyntax).Arity - fullName = MetadataHelpers.ComposeAritySuffixedMetadataName(currDiagName, arity) + fullName = MetadataHelpers.ComposeAritySuffixedMetadataName(currDiagName, arity, associatedFileIdentifier:=Nothing) End If forwardedToAssembly = GetForwardedToAssembly(containingAssembly, fullName, arity, typeSyntax, diagBag) diff --git a/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb b/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb index 59735a2f203c4..0b5ea39ab7e2b 100644 --- a/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb +++ b/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb @@ -20,7 +20,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' ''' The number of the next generated resumable state (i.e. state that resumes execution of the state machine after await expression or yield return). ''' - Private _nextState As Integer + Private _nextState As StateMachineState #If DEBUG Then ''' @@ -33,7 +33,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' Private _matchedStateCount As Integer - Public Sub New(slotAllocator As VariableSlotAllocator, firstState As Integer, increasing As Boolean) + Public Sub New(slotAllocator As VariableSlotAllocator, firstState As StateMachineState, increasing As Boolean) _increasing = increasing _slotAllocator = slotAllocator _matchedStateCount = 0 @@ -41,25 +41,25 @@ Namespace Microsoft.CodeAnalysis.VisualBasic _nextState = If(slotAllocator?.GetFirstUnusedStateMachineState(increasing), firstState) End Sub - Public Function AllocateState(awaitOrYieldReturnSyntax As SyntaxNode) As Integer + Public Function AllocateState(awaitOrYieldReturnSyntax As SyntaxNode) As StateMachineState Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(awaitOrYieldReturnSyntax)) Dim direction = If(_increasing, +1, -1) - Dim stateNumber As Integer + Dim state As StateMachineState - If _slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, stateNumber) = True Then + If _slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, state) = True Then #If DEBUG Then ' two states of the new state machine should not match the same state of the previous machine - Debug.Assert(Not _matchedStates(stateNumber * direction)) - _matchedStates(stateNumber * direction) = True + Debug.Assert(Not _matchedStates(state * direction)) + _matchedStates(state * direction) = True #End If _matchedStateCount += 1 Else - stateNumber = _nextState - _nextState += direction + state = _nextState + _nextState = CType(_nextState + direction, StateMachineState) End If - Return stateNumber + Return state End Function ''' diff --git a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb index bafab6592ced3..30f4422a89728 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb @@ -30,6 +30,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Get End Property + Private ReadOnly Property INamedTypeReferenceAssociatedFileIdentifier As String Implements Cci.INamedTypeReference.AssociatedFileIdentifier + Get + Return Nothing + End Get + End Property + Private ReadOnly Property INamedEntityName As String Implements Cci.INamedEntity.Name Get ' CCI automatically handles type suffix, so use Name instead of MetadataName diff --git a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb index 7e5181c4d4a07..c857d34202f5a 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb @@ -758,6 +758,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property INamedTypeReferenceAssociatedFileIdentifier As String Implements INamedTypeReference.AssociatedFileIdentifier + Get + Return Nothing + End Get + End Property + Private ReadOnly Property INamedEntityName As String Implements INamedEntity.Name Get ' CCI automatically adds the arity suffix, so we return Name, not MetadataName here. diff --git a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb index 449082250f976..a8b856e239863 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb @@ -443,7 +443,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit ' exported types are not emitted in EnC deltas (hence generation 0): Dim fullEmittedName As String = MetadataHelpers.BuildQualifiedName( DirectCast(typeReference, Cci.INamespaceTypeReference).NamespaceName, - Cci.MetadataWriter.GetMangledName(DirectCast(typeReference, Cci.INamedTypeReference), generation:=0)) + Cci.MetadataWriter.GetMetadataName(DirectCast(typeReference, Cci.INamedTypeReference), generation:=0)) ' First check against types declared in the primary module If ContainsTopLevelType(fullEmittedName) Then diff --git a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.Await.vb b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.Await.vb index f17a07d87530c..4589bb498960e 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.Await.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.Await.vb @@ -110,9 +110,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Function Private Function GenerateAwaitForIncompleteTask(awaiterTemp As LocalSymbol) As BoundBlock - Dim stateNumber As Integer = 0 + Dim state As StateMachineState = 0 Dim resumeLabel As GeneratedLabelSymbol = Nothing - AddResumableState(awaiterTemp.GetDeclaratorSyntax(), stateNumber, resumeLabel) + AddResumableState(awaiterTemp.GetDeclaratorSyntax(), state, resumeLabel) Dim awaiterType As TypeSymbol = awaiterTemp.Type Dim awaiterFieldType As TypeSymbol = awaiterType @@ -127,7 +127,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic blockBuilder.Add( Me.F.Assignment( Me.F.Field(Me.F.Me(), Me.StateField, True), - Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(stateNumber)))) + Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(state)))) ' Emit Await yield point to be injected into PDB blockBuilder.Add(Me.F.NoOp(NoOpStatementFlavor.AwaitYieldPoint)) @@ -253,7 +253,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic blockBuilder.Add( Me.F.Assignment( Me.F.Field(Me.F.Me(), Me.StateField, True), - Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(StateMachineStates.NotStartedStateMachine)))) + Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(StateMachineState.NotStartedOrRunningState)))) ' STMT: $awaiterTemp = Me.$awaiter ' or diff --git a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb index 3675beb28001b..5be9c3691e995 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb @@ -88,9 +88,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Me._nextAwaiterId = If(slotAllocatorOpt IsNot Nothing, slotAllocatorOpt.PreviousAwaiterSlotCount, 0) End Sub - Protected Overrides ReadOnly Property FirstIncreasingResumableState As Integer + Protected Overrides ReadOnly Property FirstIncreasingResumableState As StateMachineState Get - Return StateMachineStates.FirstResumableAsyncState + Return StateMachineState.FirstResumableAsyncState End Get End Property @@ -177,7 +177,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic exceptionLocal, Me.F.Block( SyntheticBoundNodeFactory.HiddenSequencePoint(), - Me.F.Assignment(Me.F.Field(Me.F.Me(), Me.StateField, True), Me.F.Literal(StateMachineStates.FinishedStateMachine)), + Me.F.Assignment(Me.F.Field(Me.F.Me(), Me.StateField, True), Me.F.Literal(StateMachineState.FinishedState)), Me.F.ExpressionStatement( Me._owner.GenerateMethodCall( Me.F.Field(Me.F.Me(), Me._builder, False), @@ -193,7 +193,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' STMT: state = cachedState = finishedState Dim stateDone = Me.F.Assignment( Me.F.Field(Me.F.Me(), Me.StateField, True), - Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(StateMachineStates.FinishedStateMachine))) + Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(StateMachineState.FinishedState))) Dim block As MethodBlockSyntax = TryCast(body.Syntax, MethodBlockSyntax) If (block Is Nothing) Then bodyBuilder.Add(stateDone) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.vb index d1032d750de75..930068a8a9131 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.vb @@ -216,7 +216,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic bodyBuilder.Add( Me.F.Assignment( stateFieldAsLValue, - Me.F.Literal(StateMachineStates.NotStartedStateMachine))) + Me.F.Literal(StateMachineState.NotStartedOrRunningState))) ' STAT: localStateMachine.$builder = System.Runtime.CompilerServices.AsyncTaskMethodBuilder(Of typeArgs).Create() Dim constructedBuilderField As FieldSymbol = Me._builderField.AsMember(frameType) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.IteratorMethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.IteratorMethodToClassRewriter.vb index 91f657ed0a1e0..eb4598d3edb9f 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.IteratorMethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.IteratorMethodToClassRewriter.vb @@ -38,9 +38,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic _current = current End Sub - Protected Overrides ReadOnly Property FirstIncreasingResumableState As Integer + Protected Overrides ReadOnly Property FirstIncreasingResumableState As StateMachineState Get - Return StateMachineStates.FirstResumableIteratorState + Return StateMachineState.FirstResumableIteratorState End Get End Property @@ -58,7 +58,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic F.CurrentMethod = moveNextMethod Dim initialLabel As GeneratedLabelSymbol = Nothing - AddState(StateMachineStates.InitialIteratorState, initialLabel) + AddState(StateMachineState.InitialIteratorState, initialLabel) Me._methodValue = Me.F.SynthesizedLocal(F.CurrentMethod.ReturnType, SynthesizedLocalKind.StateMachineReturnValue, F.Syntax) @@ -83,7 +83,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dispatch(isOutermost:=True), GenerateReturn(finished:=True), F.Label(initialLabel), - F.Assignment(F.Field(F.Me, Me.StateField, True), Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(StateMachineStates.NotStartedStateMachine))), + F.Assignment(F.Field(F.Me, Me.StateField, True), Me.F.AssignmentExpression(Me.F.Local(Me.CachedState, True), Me.F.Literal(StateMachineState.NotStartedOrRunningState))), newBody, HandleReturn() )) @@ -107,7 +107,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic F.Select( F.Field(F.Me, Me.StateField, False), sections), - F.Assignment(F.Field(F.Me, Me.StateField, True), F.Literal(StateMachineStates.NotStartedStateMachine)), + F.Assignment(F.Field(F.Me, Me.StateField, True), F.Literal(StateMachineState.NotStartedOrRunningState)), F.Label(breakLabel), F.ExpressionStatement(F.Call(F.Me, moveNextMethod)), F.Return() @@ -190,7 +190,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' : ' Me.state = -1 - Dim stateNumber As Integer = 0 + Dim stateNumber As StateMachineState = 0 Dim resumeLabel As GeneratedLabelSymbol = Nothing AddResumableState(node.Syntax, stateNumber, resumeLabel) @@ -201,7 +201,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic F.Assignment(F.Field(F.Me, Me.StateField, True), F.AssignmentExpression(F.Local(Me.CachedState, True), F.Literal(stateNumber))), GenerateReturn(finished:=False), F.Label(resumeLabel), - F.Assignment(F.Field(F.Me, Me.StateField, True), F.AssignmentExpression(F.Local(Me.CachedState, True), F.Literal(StateMachineStates.NotStartedStateMachine))) + F.Assignment(F.Field(F.Me, Me.StateField, True), F.AssignmentExpression(F.Local(Me.CachedState, True), F.Literal(StateMachineState.NotStartedOrRunningState))) ) ) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.vb index 125c80376913d..83aa334ed42b0 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/IteratorRewriter/IteratorRewriter.vb @@ -215,11 +215,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic F.If( condition:= F.LogicalAndAlso( - F.IntEqual(F.Field(F.Me, StateField, False), F.Literal(StateMachineStates.FinishedStateMachine)), + F.IntEqual(F.Field(F.Me, StateField, False), F.Literal(StateMachineState.FinishedState)), F.IntEqual(F.Field(F.Me, _initialThreadIdField, False), managedThreadId)), thenClause:= F.Block( - F.Assignment(F.Field(F.Me, StateField, True), F.Literal(StateMachineStates.FirstUnusedState)), + F.Assignment(F.Field(F.Me, StateField, True), F.Literal(StateMachineState.FirstUnusedState)), F.Assignment(F.Local(resultVariable, True), F.Me), If(Method.IsShared OrElse Method.MeParameter.Type.IsReferenceType, F.Goto(thisInitialized), @@ -323,7 +323,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Protected Overrides Sub InitializeStateMachine(bodyBuilder As ArrayBuilder(Of BoundStatement), frameType As NamedTypeSymbol, stateMachineLocal As LocalSymbol) ' Dim stateMachineLocal As new IteratorImplementationClass(N) ' where N is either 0 (if we're producing an enumerator) or -2 (if we're producing an enumerable) - Dim initialState = If(_isEnumerable, StateMachineStates.FinishedStateMachine, StateMachineStates.FirstUnusedState) + Dim initialState = If(_isEnumerable, StateMachineState.FinishedState, StateMachineState.FirstUnusedState) bodyBuilder.Add( F.Assignment( F.Local(stateMachineLocal, True), diff --git a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb index 92fd33e37bf90..a55cb0d640606 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb @@ -19,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Protected Friend ReadOnly F As SyntheticBoundNodeFactory Private ReadOnly _resumableStateAllocator As ResumableStateMachineStateAllocator - Private _nextFinalizerState As Integer + Private _nextFinalizerState As StateMachineState ''' ''' The "state" of the state machine that is the translation of the iterator method. @@ -61,7 +61,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' this is the state for finalization from anywhere in this try block. ''' Initially set to -1, representing the no-op finalization required at the top level. ''' - Private _currentFinalizerState As Integer = -1 + Private _currentFinalizerState As StateMachineState = StateMachineState.NotStartedOrRunningState ''' ''' The set of local variables and parameters that were hoisted and need a proxy. @@ -99,14 +99,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic _resumableStateAllocator = New ResumableStateMachineStateAllocator( slotAllocatorOpt, firstState:=FirstIncreasingResumableState, increasing:=True) - _nextFinalizerState = If(slotAllocatorOpt?.GetFirstUnusedStateMachineState(increasing:=False), StateMachineStates.FirstIteratorFinalizeState) + _nextFinalizerState = If(slotAllocatorOpt?.GetFirstUnusedStateMachineState(increasing:=False), StateMachineState.FirstIteratorFinalizeState) For Each p In initialProxies Proxies.Add(p.Key, p.Value) Next End Sub - Protected MustOverride ReadOnly Property FirstIncreasingResumableState As Integer + Protected MustOverride ReadOnly Property FirstIncreasingResumableState As StateMachineState Protected MustOverride ReadOnly Property EncMissingStateMessage As String ''' @@ -156,30 +156,30 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Sub End Structure - Protected Sub AddResumableState(awaitOrYieldReturnSyntax As SyntaxNode, ByRef stateNumber As Integer, ByRef resumeLabel As GeneratedLabelSymbol) - stateNumber = _resumableStateAllocator.AllocateState(awaitOrYieldReturnSyntax) + Protected Sub AddResumableState(awaitOrYieldReturnSyntax As SyntaxNode, ByRef state As StateMachineState, ByRef resumeLabel As GeneratedLabelSymbol) + state = _resumableStateAllocator.AllocateState(awaitOrYieldReturnSyntax) If _tryBlockSyntaxForNextFinalizerState IsNot Nothing Then If SlotAllocatorOpt Is Nothing OrElse Not SlotAllocatorOpt.TryGetPreviousStateMachineState(_tryBlockSyntaxForNextFinalizerState, _currentFinalizerState) Then _currentFinalizerState = _nextFinalizerState - _nextFinalizerState -= 1 + _nextFinalizerState = CType(_nextFinalizerState - 1, StateMachineState) End If AddStateDebugInfo(_tryBlockSyntaxForNextFinalizerState, _currentFinalizerState) _tryBlockSyntaxForNextFinalizerState = Nothing End If - AddStateDebugInfo(awaitOrYieldReturnSyntax, stateNumber) - AddState(stateNumber, resumeLabel) + AddStateDebugInfo(awaitOrYieldReturnSyntax, state) + AddState(state, resumeLabel) End Sub - Protected Sub AddStateDebugInfo(node As SyntaxNode, stateNumber As Integer) + Protected Sub AddStateDebugInfo(node As SyntaxNode, state As StateMachineState) Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(node) OrElse SyntaxBindingUtilities.BindsToTryStatement(node), $"Unexpected syntax: {node.Kind()}") Dim syntaxOffset = CurrentMethod.CalculateLocalSyntaxOffset(node.SpanStart, node.SyntaxTree) - _stateDebugInfoBuilder.Add(New StateMachineStateDebugInfo(syntaxOffset, stateNumber)) + _stateDebugInfoBuilder.Add(New StateMachineStateDebugInfo(syntaxOffset, state)) End Sub Protected Sub AddState(stateNumber As Integer, ByRef resumeLabel As GeneratedLabelSymbol) @@ -333,11 +333,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' Public Overrides Function VisitTryStatement(node As BoundTryStatement) As BoundNode Dim oldDispatches As Dictionary(Of LabelSymbol, List(Of Integer)) = Me.Dispatches - Dim oldFinalizerState As Integer = Me._currentFinalizerState + Dim oldFinalizerState As StateMachineState = Me._currentFinalizerState Dim oldTryBlockSyntax As SyntaxNode = Me._tryBlockSyntaxForNextFinalizerState Me.Dispatches = Nothing - Me._currentFinalizerState = -1 + Me._currentFinalizerState = StateMachineState.NotStartedOrRunningState Me._tryBlockSyntaxForNextFinalizerState = node.Syntax Dim tryBlock As BoundBlock = Me.F.Block(DirectCast(Me.Visit(node.TryBlock), BoundStatement)) @@ -360,7 +360,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Me.F.Label(finalizer), Me.F.Assignment( F.Field(F.Me(), Me.StateField, True), - F.AssignmentExpression(F.Local(Me.CachedState, True), F.Literal(StateMachineStates.NotStartedStateMachine))), + F.AssignmentExpression(F.Local(Me.CachedState, True), F.Literal(StateMachineState.NotStartedOrRunningState))), Me.GenerateReturn(False), Me.F.Label(skipFinalizer), tryBlock) @@ -388,7 +388,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Me.F.If( condition:=Me.F.IntLessThan( Me.F.Local(Me.CachedState, False), - Me.F.Literal(StateMachineStates.FirstUnusedState)), + Me.F.Literal(StateMachineState.FirstUnusedState)), thenClause:=DirectCast(Me.Visit(node.FinallyBlockOpt), BoundBlock)), SyntheticBoundNodeFactory.HiddenSequencePoint())) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineStates.vb b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineStates.vb deleted file mode 100644 index 1e23ee0be7571..0000000000000 --- a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineStates.vb +++ /dev/null @@ -1,22 +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. - -Imports Microsoft.Cci -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - -Namespace Microsoft.CodeAnalysis.VisualBasic - - Friend NotInheritable Class StateMachineStates - Public Const FirstIteratorFinalizeState As Integer = -3 - Public Const InitialIteratorState As Integer = 0 - Public Const FinishedStateMachine As Integer = -2 - Public Const NotStartedStateMachine As Integer = -1 - Public Const FirstUnusedState As Integer = 0 - Public Const FirstResumableIteratorState As Integer = InitialIteratorState + 1 - Public Const FirstResumableAsyncState As Integer = 0 - End Class - -End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb b/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb index 2d2ac8d4731be..21a9bf79b7314 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb @@ -448,6 +448,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return boundNode End Function + Public Function Literal(value As StateMachineState) As BoundLiteral + Return Literal(CType(value, Integer)) + End Function + Public Function BadExpression(ParamArray subExpressions As BoundExpression()) As BoundExpression Dim boundNode = New BoundBadExpression(_syntax, LookupResultKind.Empty, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(subExpressions), ErrorTypeSymbol.UnknownResultType, hasErrors:=True) boundNode.SetWasCompilerGenerated() diff --git a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt index 8b137891791fe..9a31aa7a58e6f 100644 --- a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt @@ -1 +1 @@ - +*REMOVED*Overrides Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions.GetHashCode() -> Integer diff --git a/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb b/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb index 8aa7563127965..653224d769944 100644 --- a/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb +++ b/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb @@ -19,6 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides ReadOnly Property IsCaseSensitive As Boolean = False + Public Overrides ReadOnly Property AttributeListKind As Integer = SyntaxKind.AttributeList + Public Overrides Function IsValidIdentifier(name As String) As Boolean Return SyntaxFacts.IsValidIdentifier(name) End Function @@ -83,20 +85,34 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Throw ExceptionUtilities.UnexpectedValue(node.Kind()) End Function - Public Overrides Sub AddAliases(node As SyntaxNode, aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), [global] As Boolean) + Public Overloads Function GetUnqualifiedIdentifierOfName(name As InternalSyntax.NameSyntax) As InternalSyntax.IdentifierTokenSyntax + Dim qualifiedName = TryCast(name, InternalSyntax.QualifiedNameSyntax) + If qualifiedName IsNot Nothing Then + Return qualifiedName.Right.Identifier + End If + + Dim simpleName = TryCast(name, InternalSyntax.SimpleNameSyntax) + If simpleName IsNot Nothing Then + Return simpleName.Identifier + End If + + Throw ExceptionUtilities.UnexpectedValue(name.KindText) + End Function + + Public Overrides Sub AddAliases(node As GreenNode, aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), [global] As Boolean) ' VB does not have global aliases at the syntax level. If [global] Then Return End If - Dim compilationUnit = TryCast(node, CompilationUnitSyntax) + Dim compilationUnit = TryCast(node, InternalSyntax.CompilationUnitSyntax) If compilationUnit Is Nothing Then Return End If For Each importsStatement In compilationUnit.Imports - For Each importsClause In importsStatement.ImportsClauses - ProcessImportsClause(aliases, importsClause) + For i = 0 To importsStatement.ImportsClauses.Count - 1 + ProcessImportsClause(aliases, importsStatement.ImportsClauses(i)) Next Next End Sub @@ -106,12 +122,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic For Each globalImport In vbOptions.GlobalImports Dim clause = globalImport.Clause - ProcessImportsClause(aliases, clause) + ProcessImportsClause(aliases, DirectCast(clause.Green, InternalSyntax.ImportsClauseSyntax)) Next End Sub - Private Sub ProcessImportsClause(aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), clause As ImportsClauseSyntax) - Dim importsClause = TryCast(clause, SimpleImportsClauseSyntax) + Private Sub ProcessImportsClause(aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), clause As InternalSyntax.ImportsClauseSyntax) + Dim importsClause = TryCast(clause, InternalSyntax.SimpleImportsClauseSyntax) If importsClause?.Alias IsNot Nothing Then aliases.Add((importsClause.Alias.Identifier.ValueText, GetUnqualifiedIdentifierOfName(importsClause.Name).ValueText)) End If diff --git a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb index 77b23983ca0b2..7d30aafb6182c 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb @@ -159,7 +159,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' Therefore it is a good practice to avoid type names with dots. Debug.Assert(Me.IsErrorType OrElse Not (TypeOf Me Is SourceNamedTypeSymbol) OrElse Not Name.Contains("."), "type name contains dots: " + Name) - Return If(MangleName, MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity), Name) + Return If(MangleName, MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, associatedFileIdentifier:=Nothing), Name) End Get End Property @@ -1216,6 +1216,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property INamedTypeSymbol_IsFile As Boolean Implements INamedTypeSymbol.IsFile + Get + Return False + End Get + End Property + Private ReadOnly Property INamedTypeSymbol_NativeIntegerUnderlyingType As INamedTypeSymbol Implements INamedTypeSymbol.NativeIntegerUnderlyingType Get Return Nothing diff --git a/src/Compilers/VisualBasic/Portable/VisualBasicCompilationOptions.vb b/src/Compilers/VisualBasic/Portable/VisualBasicCompilationOptions.vb index ff4d41831ce7b..5467e3776775e 100644 --- a/src/Compilers/VisualBasic/Portable/VisualBasicCompilationOptions.vb +++ b/src/Compilers/VisualBasic/Portable/VisualBasicCompilationOptions.vb @@ -1117,8 +1117,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' Creates a hashcode for this instance. ''' ''' A hashcode representing this instance. - Public Overrides Function GetHashCode() As Integer - Return Hash.Combine(MyBase.GetHashCodeHelper(), + Protected Overrides Function ComputeHashCode() As Integer + Return Hash.Combine(GetHashCodeHelper(), Hash.Combine(Hash.CombineValues(Me.GlobalImports), Hash.Combine(If(Me.RootNamespace IsNot Nothing, StringComparer.Ordinal.GetHashCode(Me.RootNamespace), 0), Hash.Combine(Me.OptionStrict, diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs index 568b79df42ad0..8a0fad77343ef 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs @@ -56,8 +56,8 @@ internal partial class AutomaticLineEnderCommandHandler : AbstractAutomaticLineE public AutomaticLineEnderCommandHandler( ITextUndoHistoryRegistry undoRegistry, IEditorOperationsFactoryService editorOperations, - IGlobalOptionService globalOptions) - : base(undoRegistry, editorOperations, globalOptions) + EditorOptionsService editorOptionsService) + : base(undoRegistry, editorOperations, editorOptionsService) { } @@ -327,7 +327,7 @@ protected override void ModifySelectedNode( CancellationToken cancellationToken) { var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); - var formattingOptions = document.GetSyntaxFormattingOptionsAsync(GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var formattingOptions = document.GetSyntaxFormattingOptionsAsync(EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); // Add braces for the selected node if (addBrace) diff --git a/src/EditorFeatures/CSharp/CommentSelection/CSharpToggleBlockCommentCommandHandler.cs b/src/EditorFeatures/CSharp/CommentSelection/CSharpToggleBlockCommentCommandHandler.cs index 7049d35a3718b..23db743bda3dc 100644 --- a/src/EditorFeatures/CSharp/CommentSelection/CSharpToggleBlockCommentCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CommentSelection/CSharpToggleBlockCommentCommandHandler.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; @@ -34,8 +35,8 @@ public CSharpToggleBlockCommentCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, ITextStructureNavigatorSelectorService navigatorSelectorService, - IGlobalOptionService globalOptions) - : base(undoHistoryRegistry, editorOperationsFactoryService, navigatorSelectorService, globalOptions) + EditorOptionsService editorOptionsService) + : base(undoHistoryRegistry, editorOperationsFactoryService, navigatorSelectorService, editorOptionsService) { } @@ -43,10 +44,10 @@ public CSharpToggleBlockCommentCommandHandler( /// Retrieves block comments near the selection in the document. /// Uses the CSharp syntax tree to find the commented spans. /// - protected override async Task> GetBlockCommentsInDocumentAsync(Document document, ITextSnapshot snapshot, + protected override ImmutableArray GetBlockCommentsInDocument(Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); // Only search for block comments intersecting the lines in the selections. return root.DescendantTrivia(linesContainingSelections) .Where(trivia => trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)) diff --git a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs index 71248fcde3133..cf66bfbffba9e 100644 --- a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs @@ -155,6 +155,11 @@ private static bool TryGetStartingNode( delimiters = startingNode.GetBrackets(); } + if (delimiters == default) + { + delimiters = startingNode.GetBraces(); + } + var (openingDelimiter, closingDelimiter) = delimiters; if (!openingDelimiter.IsKind(SyntaxKind.None) && openingDelimiter.Span.Start >= caretPosition || !closingDelimiter.IsKind(SyntaxKind.None) && closingDelimiter.Span.End <= caretPosition) @@ -194,7 +199,8 @@ private static bool MoveCaretToSemicolonPosition( SyntaxKind.CheckedExpression, SyntaxKind.UncheckedExpression, SyntaxKind.TypeOfExpression, - SyntaxKind.TupleExpression)) + SyntaxKind.TupleExpression, + SyntaxKind.SwitchExpression)) { // make sure the closing delimiter exists if (RequiredDelimiterIsMissing(currentNode)) @@ -460,7 +466,8 @@ private static bool StatementClosingDelimiterIsMissing(SyntaxNode currentNode) private static bool RequiredDelimiterIsMissing(SyntaxNode currentNode) { return currentNode.GetBrackets().closeBracket.IsMissing || - currentNode.GetParentheses().closeParen.IsMissing; + currentNode.GetParentheses().closeParen.IsMissing || + currentNode.GetBraces().closeBrace.IsMissing; } } } diff --git a/src/EditorFeatures/CSharp/ConvertNamespace/ConvertNamespaceCommandHandler.cs b/src/EditorFeatures/CSharp/ConvertNamespace/ConvertNamespaceCommandHandler.cs index 2dc085774397a..14dddf87469ff 100644 --- a/src/EditorFeatures/CSharp/ConvertNamespace/ConvertNamespaceCommandHandler.cs +++ b/src/EditorFeatures/CSharp/ConvertNamespace/ConvertNamespaceCommandHandler.cs @@ -27,6 +27,8 @@ using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement { @@ -50,17 +52,24 @@ internal sealed class ConvertNamespaceCommandHandler : IChainedCommandHandler nextCommandHandler) @@ -122,10 +131,10 @@ public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, return default; var cancellationToken = executionContext.OperationContext.UserCancellationToken; - var root = (CompilationUnitSyntax)document.GetRequiredSyntaxRootSynchronously(cancellationToken); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); // User has to be *after* an identifier token. - var token = root.FindToken(caret); + var token = parsedDocument.Root.FindToken(caret); if (token.Kind() != SyntaxKind.IdentifierToken) return default; @@ -144,13 +153,11 @@ public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, return default; // Pass in our special options, and C#10 so that if we can convert this to file-scoped, we will. - if (!ConvertNamespaceAnalysis.CanOfferUseFileScoped(s_fileScopedNamespacePreferenceOption, root, namespaceDecl, forAnalyzer: true, LanguageVersion.CSharp10)) + if (!ConvertNamespaceAnalysis.CanOfferUseFileScoped(s_fileScopedNamespacePreferenceOption, (CompilationUnitSyntax)parsedDocument.Root, namespaceDecl, forAnalyzer: true, LanguageVersion.CSharp10)) return default; - var formattingOptions = document.GetSyntaxFormattingOptionsAsync(_globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); - var (converted, semicolonSpan) = ConvertNamespaceTransform.ConvertNamespaceDeclarationAsync(document, namespaceDecl, formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken); - var text = converted.GetTextSynchronously(cancellationToken); - return (text, semicolonSpan); + var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false); + return ConvertNamespaceTransform.ConvertNamespaceDeclaration(parsedDocument, namespaceDecl, formattingOptions, cancellationToken); } } } diff --git a/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs b/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs index de54c6c2ef306..7774c0c59432b 100644 --- a/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs +++ b/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; @@ -30,8 +31,8 @@ public DocumentationCommentCommandHandler( IUIThreadOperationExecutor uiThreadOperationExecutor, ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) - : base(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService, globalOptions) + EditorOptionsService editorOptionsService) + : base(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService, editorOptionsService) { } diff --git a/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs b/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs index fb1bd597a45ba..c2987c58d4ca1 100644 --- a/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs +++ b/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs @@ -28,17 +28,13 @@ internal partial class CSharpFormattingInteractionService : IFormattingInteracti // All the characters that might potentially trigger formatting when typed private static readonly char[] _supportedChars = ";{}#nte:)".ToCharArray(); - private readonly IIndentationManagerService _indentationManager; - private readonly IEditorOptionsFactoryService _editorOptionsFactory; - private readonly IGlobalOptionService _globalOptions; + private readonly EditorOptionsService _editorOptionsService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpFormattingInteractionService(IIndentationManagerService indentationManager, IEditorOptionsFactoryService editorOptionsFactory, IGlobalOptionService globalOptions) + public CSharpFormattingInteractionService(EditorOptionsService editorOptionsService) { - _indentationManager = indentationManager; - _editorOptionsFactory = editorOptionsFactory; - _globalOptions = globalOptions; + _editorOptionsService = editorOptionsService; } public bool SupportsFormatDocument => true; @@ -48,7 +44,7 @@ public CSharpFormattingInteractionService(IIndentationManagerService indentation public bool SupportsFormattingOnTypedCharacter(Document document, char ch) { - var isSmartIndent = _globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp) == FormattingOptions2.IndentStyle.Smart; + var isSmartIndent = _editorOptionsService.GlobalOptions.GetOption(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp) == FormattingOptions2.IndentStyle.Smart; // We consider the proper placement of a close curly or open curly when it is typed at // the start of the line to be a smart-indentation operation. As such, even if "format @@ -61,7 +57,7 @@ public bool SupportsFormattingOnTypedCharacter(Document document, char ch) return true; } - var options = _globalOptions.GetAutoFormattingOptions(LanguageNames.CSharp); + var options = _editorOptionsService.GlobalOptions.GetAutoFormattingOptions(LanguageNames.CSharp); // If format-on-typing is not on, then we don't support formatting on any other characters. var autoFormattingOnTyping = options.FormatOnTyping; @@ -89,56 +85,44 @@ public bool SupportsFormattingOnTypedCharacter(Document document, char ch) return _supportedChars.Contains(ch); } - public async Task> GetFormattingChangesAsync( + public Task> GetFormattingChangesAsync( Document document, ITextBuffer textBuffer, TextSpan? textSpan, CancellationToken cancellationToken) { - var fallbackOptions = _globalOptions.GetCSharpSyntaxFormattingOptions(); - var options = _indentationManager.GetInferredFormattingOptions(textBuffer, _editorOptionsFactory, document.Project.LanguageServices, fallbackOptions, explicitFormat: true); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var options = textBuffer.GetSyntaxFormattingOptions(_editorOptionsService, parsedDocument.LanguageServices, explicitFormat: true); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var span = textSpan ?? new TextSpan(0, root.FullSpan.Length); - var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, span); + var span = textSpan ?? new TextSpan(0, parsedDocument.Root.FullSpan.Length); + var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(parsedDocument.Root, span); - var services = document.Project.Solution.Workspace.Services; - return Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), services, options, cancellationToken).ToImmutableArray(); + return Task.FromResult(Formatter.GetFormattedTextChanges(parsedDocument.Root, SpecializedCollections.SingletonEnumerable(formattingSpan), parsedDocument.LanguageServices.WorkspaceServices, options, cancellationToken).ToImmutableArray()); } - public async Task> GetFormattingChangesOnPasteAsync(Document document, ITextBuffer textBuffer, TextSpan textSpan, CancellationToken cancellationToken) + public Task> GetFormattingChangesOnPasteAsync(Document document, ITextBuffer textBuffer, TextSpan textSpan, CancellationToken cancellationToken) { - var fallbackOptions = _globalOptions.GetCSharpSyntaxFormattingOptions(); - var options = _indentationManager.GetInferredFormattingOptions(textBuffer, _editorOptionsFactory, document.Project.LanguageServices, fallbackOptions, explicitFormat: true); - var service = document.GetRequiredLanguageService(); - var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - return service.GetFormattingChangesOnPaste(documentSyntax, textSpan, options, cancellationToken); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var options = textBuffer.GetSyntaxFormattingOptions(_editorOptionsService, parsedDocument.LanguageServices, explicitFormat: true); + var service = parsedDocument.LanguageServices.GetRequiredService(); + return Task.FromResult(service.GetFormattingChangesOnPaste(parsedDocument, textSpan, options, cancellationToken)); } - Task> IFormattingInteractionService.GetFormattingChangesOnReturnAsync( - Document document, int caretPosition, CancellationToken cancellationToken) + public Task> GetFormattingChangesOnReturnAsync(Document document, int caretPosition, CancellationToken cancellationToken) => SpecializedTasks.EmptyImmutableArray(); - public async Task> GetFormattingChangesAsync(Document document, ITextBuffer textBuffer, char typedChar, int position, CancellationToken cancellationToken) + public Task> GetFormattingChangesAsync(Document document, ITextBuffer textBuffer, char typedChar, int position, CancellationToken cancellationToken) { - var service = document.GetRequiredLanguageService(); - var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var service = parsedDocument.LanguageServices.GetRequiredService(); - if (service.ShouldFormatOnTypedCharacter(documentSyntax, typedChar, position, cancellationToken)) + if (service.ShouldFormatOnTypedCharacter(parsedDocument, typedChar, position, cancellationToken)) { - var fallbackOptions = _globalOptions.GetCSharpSyntaxFormattingOptions(); - var formattingOptions = _indentationManager.GetInferredFormattingOptions(textBuffer, _editorOptionsFactory, document.Project.LanguageServices, fallbackOptions, explicitFormat: false); - - var indentationOptions = new IndentationOptions(formattingOptions) - { - AutoFormattingOptions = _globalOptions.GetAutoFormattingOptions(LanguageNames.CSharp), - IndentStyle = _globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp) - }; - - return service.GetFormattingChangesOnTypedCharacter(documentSyntax, position, indentationOptions, cancellationToken); + var indentationOptions = textBuffer.GetIndentationOptions(_editorOptionsService, parsedDocument.LanguageServices, explicitFormat: false); + return Task.FromResult(service.GetFormattingChangesOnTypedCharacter(parsedDocument, position, indentationOptions, cancellationToken)); } - return ImmutableArray.Empty; + return SpecializedTasks.EmptyImmutableArray(); } } } diff --git a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs index c50a8d6a1d503..5b326335ba55d 100644 --- a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs +++ b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; @@ -22,17 +23,23 @@ internal partial class RawStringLiteralCommandHandler private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private readonly IGlobalOptionService _globalOptions; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; + private readonly EditorOptionsService _editorOptionsService; + private readonly IIndentationManagerService _indentationManager; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RawStringLiteralCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, IGlobalOptionService globalOptions, - IEditorOperationsFactoryService editorOperationsFactoryService) + IEditorOperationsFactoryService editorOperationsFactoryService, + EditorOptionsService editorOptionsService, + IIndentationManagerService indentationManager) { _undoHistoryRegistry = undoHistoryRegistry; _globalOptions = globalOptions; _editorOperationsFactoryService = editorOperationsFactoryService; + _editorOptionsService = editorOptionsService; + _indentationManager = indentationManager; } public string DisplayName => CSharpEditorResources.Split_raw_string; diff --git a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs index 70b96ea23d97f..8f43bf9178941 100644 --- a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs +++ b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Indentation; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; @@ -87,8 +88,9 @@ private bool SplitRawString(ITextView textView, ITextBuffer subjectBuffer, int p if (document == null) return false; - var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); - var token = root.FindToken(position); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + + var token = parsedDocument.Root.FindToken(position); if (token.Kind() is not (SyntaxKind.SingleLineRawStringLiteralToken or SyntaxKind.MultiLineRawStringLiteralToken or SyntaxKind.InterpolatedSingleLineRawStringStartToken or @@ -97,8 +99,8 @@ SyntaxKind.InterpolatedSingleLineRawStringStartToken or return false; } - var indentationOptions = document.GetIndentationOptionsAsync(_globalOptions, cancellationToken).WaitAndGetResult(cancellationToken); - var indentation = token.GetPreferredIndentation(document, indentationOptions, cancellationToken); + var indentationOptions = subjectBuffer.GetIndentationOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false); + var indentation = token.GetPreferredIndentation(parsedDocument, indentationOptions, cancellationToken); var newLine = indentationOptions.FormattingOptions.NewLine; @@ -107,11 +109,8 @@ SyntaxKind.InterpolatedSingleLineRawStringStartToken or var edit = subjectBuffer.CreateEdit(); - var sourceText = document.GetTextSynchronously(cancellationToken); - var textToInsert = $"{newLine}{newLine}{indentation}"; - // apply the change: - edit.Insert(position, textToInsert); + edit.Insert(position, newLine + newLine + indentation); var snapshot = edit.Apply(); // move caret: diff --git a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs index 01a7a3e82eab1..4cff5b227bd46 100644 --- a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs +++ b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs @@ -33,19 +33,19 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.SplitStringLiteral internal partial class SplitStringLiteralCommandHandler : ICommandHandler { private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; - private readonly IGlobalOptionService _globalOptions; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; + private readonly EditorOptionsService _editorOptionsService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public SplitStringLiteralCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, - IGlobalOptionService globalOptions, - IEditorOperationsFactoryService editorOperationsFactoryService) + IEditorOperationsFactoryService editorOperationsFactoryService, + EditorOptionsService editorOptionsService) { _undoHistoryRegistry = undoHistoryRegistry; - _globalOptions = globalOptions; _editorOperationsFactoryService = editorOperationsFactoryService; + _editorOptionsService = editorOptionsService; } public string DisplayName => CSharpEditorResources.Split_string; @@ -58,7 +58,7 @@ public bool ExecuteCommand(ReturnKeyCommandArgs args, CommandExecutionContext co public bool ExecuteCommandWorker(ReturnKeyCommandArgs args) { - if (!_globalOptions.GetOption(SplitStringLiteralOptions.Enabled, LanguageNames.CSharp)) + if (!_editorOptionsService.GlobalOptions.GetOption(SplitStringLiteralOptions.Enabled, LanguageNames.CSharp)) { return false; } @@ -92,8 +92,7 @@ public bool ExecuteCommandWorker(ReturnKeyCommandArgs args) } } - var useTabs = !textView.Options.IsConvertTabsToSpacesEnabled(); - var tabSize = textView.Options.GetTabSize(); + IndentationOptions? lazyOptions = null; // We now go through the verified string literals and split each of them. // The list of spans is traversed in reverse order so we do not have to @@ -101,7 +100,7 @@ public bool ExecuteCommandWorker(ReturnKeyCommandArgs args) // from splitting at earlier caret positions. foreach (var span in spans.Reverse()) { - if (!SplitString(textView, subjectBuffer, span.Start.Position, useTabs, tabSize, CancellationToken.None)) + if (!SplitString(textView, subjectBuffer, span.Start.Position, ref lazyOptions, CancellationToken.None)) { return false; } @@ -110,7 +109,7 @@ public bool ExecuteCommandWorker(ReturnKeyCommandArgs args) return true; } - private bool SplitString(ITextView textView, ITextBuffer subjectBuffer, int position, bool useTabs, int tabSize, CancellationToken cancellationToken) + private bool SplitString(ITextView textView, ITextBuffer subjectBuffer, int position, ref IndentationOptions? lazyOptions, CancellationToken cancellationToken) { var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) @@ -118,19 +117,20 @@ private bool SplitString(ITextView textView, ITextBuffer subjectBuffer, int posi return false; } - // TODO: read option from textView.Options (https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1412138) - var options = document.GetIndentationOptionsAsync(_globalOptions, cancellationToken).WaitAndGetResult(cancellationToken); + lazyOptions ??= subjectBuffer.GetIndentationOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false); using var transaction = CaretPreservingEditTransaction.TryCreate( CSharpEditorResources.Split_string, textView, _undoHistoryRegistry, _editorOperationsFactoryService); - var splitter = StringSplitter.TryCreate(document, position, options, useTabs, tabSize, cancellationToken); - if (splitter?.TrySplit(out var newDocument, out var newPosition) != true) + var parsedDocument = ParsedDocument.CreateSynchronously(document, CancellationToken.None); + var splitter = StringSplitter.TryCreate(parsedDocument, position, lazyOptions.Value, cancellationToken); + if (splitter?.TrySplit(out var newRoot, out var newPosition) != true) { return false; } // apply the change: + var newDocument = document.WithSyntaxRoot(newRoot!); var workspace = newDocument.Project.Solution.Workspace; workspace.TryApplyChanges(newDocument.Project.Solution); diff --git a/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler.cs b/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler.cs index afedeee6fbb9e..7f5f7b5ce1471 100644 --- a/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler.cs +++ b/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Microsoft.VisualStudio.Text.Operations; @@ -57,6 +58,8 @@ internal partial class StringCopyPasteCommandHandler : private readonly IThreadingContext _threadingContext; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; + private readonly EditorOptionsService _editorOptionsService; + private readonly IIndentationManagerService _indentationManager; private readonly IGlobalOptionService _globalOptions; private readonly ITextBufferFactoryService2 _textBufferFactoryService; @@ -67,13 +70,17 @@ public StringCopyPasteCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, IGlobalOptionService globalOptions, - ITextBufferFactoryService2 textBufferFactoryService) + ITextBufferFactoryService2 textBufferFactoryService, + EditorOptionsService editorOptionsService, + IIndentationManagerService indentationManager) { _threadingContext = threadingContext; _undoHistoryRegistry = undoHistoryRegistry; _editorOperationsFactoryService = editorOperationsFactoryService; _globalOptions = globalOptions; _textBufferFactoryService = textBufferFactoryService; + _editorOptionsService = editorOptionsService; + _indentationManager = indentationManager; } public string DisplayName => nameof(StringCopyPasteCommandHandler); @@ -122,12 +129,12 @@ public void ExecuteCommand(PasteCommandArgs args, Action nextCommandHandler, Com return; var cancellationToken = executionContext.OperationContext.UserCancellationToken; + var parsedDocumentBeforePaste = ParsedDocument.CreateSynchronously(documentBeforePaste, cancellationToken); // When pasting, only do anything special if the user selections were entirely inside a single string // token/expression. Otherwise, we have a multi-selection across token kinds which will be extremely // complex to try to reconcile. - var stringExpressionBeforePaste = TryGetCompatibleContainingStringExpression( - documentBeforePaste, selectionsBeforePaste, cancellationToken); + var stringExpressionBeforePaste = TryGetCompatibleContainingStringExpression(parsedDocumentBeforePaste, selectionsBeforePaste); if (stringExpressionBeforePaste == null) return; @@ -135,7 +142,7 @@ public void ExecuteCommand(PasteCommandArgs args, Action nextCommandHandler, Com // token/expression. If the editor decided to make changes outside of the string, we definitely do not want // to do anything here. var stringExpressionBeforePasteFromChanges = TryGetCompatibleContainingStringExpression( - documentBeforePaste, new NormalizedSnapshotSpanCollection(snapshotBeforePaste, snapshotBeforePaste.Version.Changes.Select(c => c.OldSpan)), cancellationToken); + parsedDocumentBeforePaste, new NormalizedSnapshotSpanCollection(snapshotBeforePaste, snapshotBeforePaste.Version.Changes.Select(c => c.OldSpan))); if (stringExpressionBeforePaste != stringExpressionBeforePasteFromChanges) return; @@ -191,7 +198,7 @@ ImmutableArray GetEdits(CancellationToken cancellationToken) { var newLine = textView.Options.GetNewLineCharacter(); var indentationWhitespace = DetermineIndentationWhitespace( - documentBeforePaste, snapshotBeforePaste.AsText(), stringExpressionBeforePaste, cancellationToken); + parsedDocumentBeforePaste, subjectBuffer, snapshotBeforePaste.AsText(), stringExpressionBeforePaste, cancellationToken); // See if this is a paste of the last copy that we heard about. var edits = TryGetEditsFromKnownCopySource(newLine, indentationWhitespace); @@ -236,7 +243,8 @@ ImmutableArray TryGetEditsFromKnownCopySource( } private string DetermineIndentationWhitespace( - Document documentBeforePaste, + ParsedDocument documentBeforePaste, + ITextBuffer textBuffer, SourceText textBeforePaste, ExpressionSyntax stringExpressionBeforePaste, CancellationToken cancellationToken) @@ -256,7 +264,7 @@ private string DetermineIndentationWhitespace( // Otherwise, we have a single-line raw string. Determine the default indentation desired here. // We'll use that if we have to convert this single-line raw string to a multi-line one. - var indentationOptions = documentBeforePaste.GetIndentationOptionsAsync(_globalOptions, cancellationToken).WaitAndGetResult(cancellationToken); + var indentationOptions = textBuffer.GetIndentationOptions(_editorOptionsService, documentBeforePaste.LanguageServices, explicitFormat: false); return stringExpressionBeforePaste.GetFirstToken().GetPreferredIndentation(documentBeforePaste, indentationOptions, cancellationToken); } @@ -318,18 +326,15 @@ private static bool ContentsAreSame( /// anything special as trying to correct in this scenario is too difficult. /// private static ExpressionSyntax? TryGetCompatibleContainingStringExpression( - Document document, NormalizedSnapshotSpanCollection spans, CancellationToken cancellationToken) + ParsedDocument document, NormalizedSnapshotSpanCollection spans) { if (spans.Count == 0) return null; var snapshot = spans[0].Snapshot; - var root = document.GetSyntaxRootSynchronously(cancellationToken); - if (root == null) - return null; // First, try to see if all the selections are at least contained within a single string literal expression. - var stringExpression = FindCommonContainingStringExpression(root, spans); + var stringExpression = FindCommonContainingStringExpression(document.Root, spans); if (stringExpression == null) return null; diff --git a/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler_CutCopy.cs b/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler_CutCopy.cs index eee60f8462d26..33b9ef9e8990b 100644 --- a/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler_CutCopy.cs +++ b/src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteCommandHandler_CutCopy.cs @@ -54,8 +54,7 @@ private void ExecuteCutOrCopyCommand(ITextView textView, ITextBuffer subjectBuff // Always try to store our data to the clipboard (if we have access to the clipboard service). Even if we // didn't capture any useful data, we want to store that to blow away any prior stored data we have. - if (copyPasteService != null) - copyPasteService.TrySetClipboardData(KeyAndVersion, dataToStore ?? ""); + copyPasteService?.TrySetClipboardData(KeyAndVersion, dataToStore ?? ""); } private static (string? dataToStore, IStringCopyPasteService service) CaptureCutCopyInformation( @@ -69,6 +68,8 @@ private static (string? dataToStore, IStringCopyPasteService service) CaptureCut if (copyPasteService == null) return default; + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var spans = textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer); // We only support smart copy/paste when a single selection is copied (and a single selection is pasted @@ -87,7 +88,7 @@ private static (string? dataToStore, IStringCopyPasteService service) CaptureCut } var stringExpression = TryGetCompatibleContainingStringExpression( - document, new NormalizedSnapshotSpanCollection(span), cancellationToken); + parsedDocument, new NormalizedSnapshotSpanCollection(span)); if (stringExpression is null) return default; diff --git a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs index 96153086229fa..79d3f66e1eb67 100644 --- a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs +++ b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs @@ -7,10 +7,12 @@ using Microsoft.CodeAnalysis.BraceCompletion; using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.Editor.UnitTests.AutomaticCompletion; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Text.Editor; using Roslyn.Test.Utilities; using Xunit; using static Microsoft.CodeAnalysis.BraceCompletion.AbstractBraceCompletionService; @@ -784,11 +786,12 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } + }; + + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -859,11 +862,12 @@ class Goo { public int bar; }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } + }; + + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -930,11 +934,12 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } + }; + + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -991,11 +996,12 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } + }; + + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1052,11 +1058,11 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, false } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1116,13 +1122,15 @@ public void X() } }"; + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { AutoFormattingOptionsStorage.FormatOnCloseBrace, false }, + { FormattingOptions2.SmartIndent, FormattingOptions2.IndentStyle.Block }, + }; - using var session = CreateSession(code); + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); - session.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(AutoFormattingOptionsStorage.FormatOnCloseBrace, LanguageNames.CSharp), false); - session.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(FormattingOptions.SmartIndent, LanguageNames.CSharp), FormattingOptions.IndentStyle.Block); - CheckStart(session.Session); Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText()); @@ -1145,10 +1153,13 @@ public class C1 { } }"; - using var session = CreateSession(code); - Assert.NotNull(session); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { FormattingOptions2.SmartIndent, FormattingOptions2.IndentStyle.None }, + }; - session.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(FormattingOptions.SmartIndent, LanguageNames.CSharp), FormattingOptions.IndentStyle.None); + using var session = CreateSession(code, globalOptions); + Assert.NotNull(session); CheckStart(session.Session); Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText()); @@ -1178,10 +1189,13 @@ public class C1 } }"; - using var session = CreateSession(code); - Assert.NotNull(session); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { FormattingOptions2.SmartIndent, FormattingOptions2.IndentStyle.Block }, + }; - session.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(FormattingOptions.SmartIndent, LanguageNames.CSharp), FormattingOptions.IndentStyle.Block); + using var session = CreateSession(code, globalOptions); + Assert.NotNull(session); CheckStart(session.Session); Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText()); @@ -1219,10 +1233,13 @@ public class C1 } }"; - using var session = CreateSession(code); - Assert.NotNull(session); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { FormattingOptions2.SmartIndent, FormattingOptions2.IndentStyle.Block }, + }; - session.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(FormattingOptions.SmartIndent, LanguageNames.CSharp), FormattingOptions.IndentStyle.Block); + using var session = CreateSession(code, globalOptions); + Assert.NotNull(session); CheckStart(session.Session); Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText()); @@ -1323,11 +1340,11 @@ public void CurlyBraceFormatting_InsertsCorrectNewLine() { var code = @"class C $$"; - var optionSet = new Dictionary + var globalOptions = new OptionsCollection(LanguageNames.CSharp) { - { new OptionKey2(FormattingOptions2.NewLine, LanguageNames.CSharp), "\r" } + { FormattingOptions2.NewLine, "\r" } }; - using var session = CreateSession(code, optionSet); + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1366,11 +1383,11 @@ public void man(R r) } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1410,11 +1427,11 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1454,11 +1471,11 @@ public int I } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInAccessors, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInAccessors, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1498,11 +1515,11 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInAnonymousMethods, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInAnonymousMethods, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1542,11 +1559,11 @@ public void man() } } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInAnonymousTypes, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInAnonymousTypes, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1587,11 +1604,11 @@ public void man() } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInControlBlocks, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInControlBlocks, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); @@ -1638,22 +1655,22 @@ public void man() } }"; - var optionSet = new Dictionary - { - { CSharpFormattingOptions2.NewLinesForBracesInControlBlocks, bracesOnNewLine } - }; - using var session = CreateSession(code, optionSet); + var globalOptions = new OptionsCollection(LanguageNames.CSharp) + { + { CSharpFormattingOptions2.NewLinesForBracesInControlBlocks, bracesOnNewLine } + }; + using var session = CreateSession(code, globalOptions); Assert.NotNull(session); CheckStart(session.Session); CheckReturn(session.Session, 12, expected); } - internal static Holder CreateSession(string code, Dictionary? optionSet = null) + internal static Holder CreateSession(string code, OptionsCollection? globalOptions = null) { return CreateSession( TestWorkspace.CreateCSharp(code), - CurlyBrace.OpenCharacter, CurlyBrace.CloseCharacter, optionSet); + CurlyBrace.OpenCharacter, CurlyBrace.CloseCharacter, globalOptions); } } } diff --git a/src/EditorFeatures/CSharpTest/CommentSelection/CSharpCommentSelectionTests.cs b/src/EditorFeatures/CSharpTest/CommentSelection/CSharpCommentSelectionTests.cs index e2b9883499aa6..53d6786c41398 100644 --- a/src/EditorFeatures/CSharpTest/CommentSelection/CSharpCommentSelectionTests.cs +++ b/src/EditorFeatures/CSharpTest/CommentSelection/CSharpCommentSelectionTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CommentSelection; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -119,7 +120,7 @@ private static void UncommentSelection(string markup, string expected) var commandHandler = new CommentUncommentSelectionCommandHandler( workspace.GetService(), workspace.GetService(), - workspace.GlobalOptions); + workspace.GetService()); var textView = doc.GetTextView(); var textBuffer = doc.GetTextBuffer(); diff --git a/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs index a0dc626ee8d0f..a7f28f7d37c32 100644 --- a/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs @@ -4245,6 +4245,60 @@ public int XValue globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon), false); }); } + + [WpfFact] + public void TestSwitchExpression() + { + var code = @" +public class Bar +{ + public void Test(string myString) + { + var a = myString switch + { + ""Hello"" => 1, + ""World"" => 2, + _ => 3$$ + } + } +}"; + + var expected = @" +public class Bar +{ + public void Test(string myString) + { + var a = myString switch + { + ""Hello"" => 1, + ""World"" => 2, + _ => 3 + };$$ + } +}"; + VerifyTypingSemicolon(code, expected); + } + + [WpfFact] + public void TestNotInBracesSwitchExpression() + { + var code = @" +public class Bar +{ + public void Test(string myString) + { + var a = myString switch + $${ + ""Hello"" => 1, + ""World"" => 2, + _ => 3 + } + } +}"; + + VerifyNoSpecialSemicolonHandling(code); + } + protected override TestWorkspace CreateTestWorkspace(string code) => TestWorkspace.CreateCSharp(code); } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs index 74fa9db644188..9493420581e9b 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -64,6 +65,7 @@ public void TestCompletionProviderOrder() typeof(ExtensionMethodImportCompletionProvider), typeof(AggregateEmbeddedLanguageCompletionProvider), typeof(FunctionPointerUnmanagedCallingConventionCompletionProvider), + typeof(CSharpSnippetCompletionProvider), // Built-in interactive providers typeof(LoadDirectiveCompletionProvider), diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs index 5c3d6722ef93b..cfbc1cf8ddd02 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs @@ -684,5 +684,151 @@ class C await VerifyItemIsAbsentAsync(markup, "required"); } + + [Fact] + public async Task SuggestFileOnTypes() + { + var markup = $$""" + $$ class C { } + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task DoNotSuggestFileAfterFile() + { + var markup = $$""" + file $$ + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileAfterReadonly() + { + // e.g. 'readonly file struct X { }' + var markup = $$""" + readonly $$ + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileBeforeFileType() + { + var markup = $$""" + $$ + + file class C { } + """; + + // it might seem like we want to prevent 'file file class', + // but it's likely the user is declaring a file type above an existing file type here. + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileBeforeDelegate() + { + var markup = $$""" + $$ delegate + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task DoNotSuggestFileOnNestedTypes() + { + var markup = $$""" + class Outer + { + $$ class C { } + } + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Fact] + public async Task DoNotSuggestFileOnNonTypeMembers() + { + var markup = $$""" + class C + { + $$ + } + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Theory] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected")] + [InlineData("private")] + public async Task DoNotSuggestFileAfterFilteredKeywords(string keyword) + { + var markup = $$""" + {{keyword}} $$ + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Theory] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected")] + [InlineData("private")] + public async Task DoNotSuggestFilteredKeywordsAfterFile(string keyword) + { + var markup = $$""" + file $$ + """; + + await VerifyItemIsAbsentAsync(markup, keyword); + } + + [Fact] + public async Task SuggestFileInFileScopedNamespace() + { + var markup = $$""" + namespace NS; + + $$ + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileInNamespace() + { + var markup = $$""" + namespace NS + { + $$ + } + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileAfterClass() + { + var markup = $$""" + file class C { } + + $$ + """; + + await VerifyItemExistsAsync(markup, "file"); + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs index 34e0458e21273..c296459fd3ad9 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs @@ -996,6 +996,28 @@ static void Main(string[] args) await VerifyItemIsAbsentAsync(markup, "this"); } + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task RequiredMembersLabeledAndSelected() + { + var markup = @" +class C +{ + public required int RequiredField; + public required int RequiredProperty { get; set; } +} + +class D +{ + static void Main(string[] args) + { + var t = new C() { $$ }; + } +}"; + + await VerifyItemExistsAsync(markup, "RequiredField", inlineDescription: FeaturesResources.Required, matchPriority: MatchPriority.Preselect); + await VerifyItemExistsAsync(markup, "RequiredProperty", inlineDescription: FeaturesResources.Required); + } + [WorkItem(15205, "https://github.com/dotnet/roslyn/issues/15205")] [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task NestedPropertyInitializers1() diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.cs new file mode 100644 index 0000000000000..d885da86b410d --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.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 System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + public abstract class AbstractCSharpSnippetCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + protected abstract string ItemToCommit { get; } + + protected AbstractCSharpSnippetCompletionProviderTests() + { + ShowNewSnippetExperience = true; + } + + internal override Type GetCompletionProviderType() + => typeof(CSharpSnippetCompletionProvider); + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs new file mode 100644 index 0000000000000..6ffacd38dfa24 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs @@ -0,0 +1,423 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + public class CSharpConsoleSnippetCompletionProviderTests : AbstractCSharpSnippetCompletionProviderTests + { + protected override string ItemToCommit => FeaturesResources.Write_to_the_console; + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetInMethodTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Wr$$ + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +class Program +{ + public void Method() + { + Console.WriteLine($$); + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertAsyncConsoleSnippetTest() + { + var markupBeforeCommit = +@"class Program +{ + public async Task MethodAsync() + { + Wr$$ + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +class Program +{ + public async Task MethodAsync() + { + await Console.Out.WriteLineAsync($$); + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetGlobalTest() + { + var markupBeforeCommit = +@"$$ +class Program +{ + public async Task MethodAsync() + { + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +Console.WriteLine($$); + +class Program +{ + public async Task MethodAsync() + { + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInBlockNamespaceTest() + { + var markupBeforeCommit = +@" +namespace Namespace +{ + $$ + class Program + { + public async Task MethodAsync() + { + } + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInFileScopedNamespaceTest() + { + var markupBeforeCommit = +@" +namespace Namespace; +$$ +class Program +{ + public async Task MethodAsync() + { + } +} +"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetInConstructorTest() + { + var markupBeforeCommit = +@"class Program +{ + public Program() + { + var x = 5; + $$ + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +class Program +{ + public Program() + { + var x = 5; + Console.WriteLine($$); + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + /// + /// Simplifier does not work as intended, once that changes this outcome + /// should be able to simplify the inserted snippet. + /// + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetInLocalFunctionTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var x = 5; + void LocalMethod() + { + $$ + } + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +class Program +{ + public void Method() + { + var x = 5; + void LocalMethod() + { + global::System.Console.WriteLine($$); + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + /// + /// Simplifier does not work as intended, once that changes this outcome + /// should be able to simplify the inserted snippet. + /// + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetInAnonymousFunctionTest() + { + var markupBeforeCommit = +@"public delegate void Print(int value); + +static void Main(string[] args) +{ + Print print = delegate(int val) { + $$ + }; + +}"; + + var expectedCodeAfterCommit = +@"using System; + +public delegate void Print(int value); + +static void Main(string[] args) +{ + Print print = delegate(int val) { + global::System.Console.WriteLine($$); + }; + +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + /// + /// Simplifier does not work as intended, once that changes this outcome + /// should be able to simplify the inserted snippet. + /// + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetInParenthesizedLambdaExpressionTest() + { + var markupBeforeCommit = +@" +Func testForEquality = (x, y) => +{ + $$ + return x == y; +};"; + + var expectedCodeAfterCommit = +@" +using System; + +Func testForEquality = (x, y) => +{ + global::System.Console.WriteLine($$); + return x == y; +};"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInSwitchExpression() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var operation = 2; + + var result = operation switch + { + $$ + 1 => ""Case 1"", + 2 => ""Case 2"", + 3 => ""Case 3"", + 4 => ""Case 4"", + }; + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInSingleLambdaExpression() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Func f = x => $$; + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInStringTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var str = ""$$""; + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInObjectInitializerTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var str = new Test($$); + } +} + +class Test +{ + private string val; + + public Test(string val) + { + this.val = val; + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInParameterListTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method(int x, $$) + { + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInRecordDeclarationTest() + { + var markupBeforeCommit = +@"public record Person +{ + $$ + public string FirstName { get; init; } = default!; + public string LastName { get; init; } = default!; +};"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoConsoleSnippetInVariableDeclarationTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var x = $$ + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetWithInvocationBeforeAndAfterCursorTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Wr$$Blah + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +class Program +{ + public void Method() + { + Console.WriteLine($$); + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetWithInvocationUnderscoreBeforeAndAfterCursorTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + _Wr$$Blah_ + } +}"; + + var expectedCodeAfterCommit = +@"using System; + +class Program +{ + public void Method() + { + Console.WriteLine($$); + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs new file mode 100644 index 0000000000000..3f8ce882649c9 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -0,0 +1,380 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + public class CSharpIfSnippetCompletionProviderTests : AbstractCSharpSnippetCompletionProviderTests + { + protected override string ItemToCommit => FeaturesResources.Insert_an_if_statement; + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInMethodTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Ins$$ + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + if (true) + {$$ + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInGlobalContextTest() + { + var markupBeforeCommit = +@"Ins$$ +"; + + var expectedCodeAfterCommit = +@"if (true) +{$$ +} +"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInBlockNamespaceTest() + { + var markupBeforeCommit = +@" +namespace Namespace +{ + $$ + class Program + { + public async Task MethodAsync() + { + } + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInFileScopedNamespaceTest() + { + var markupBeforeCommit = +@" +namespace Namespace; +$$ +class Program +{ + public async Task MethodAsync() + { + } +} +"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInConstructorTest() + { + var markupBeforeCommit = +@"class Program +{ + public Program() + { + var x = 5; + $$ + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public Program() + { + var x = 5; + if (true) + {$$ + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippettInLocalFunctionTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var x = 5; + void LocalMethod() + { + $$ + } + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + var x = 5; + void LocalMethod() + { + if (true) + {$$ + } + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInAnonymousFunctionTest() + { + var markupBeforeCommit = +@"public delegate void Print(int value); + +static void Main(string[] args) +{ + Print print = delegate(int val) { + $$ + }; + +}"; + + var expectedCodeAfterCommit = +@"public delegate void Print(int value); + +static void Main(string[] args) +{ + Print print = delegate(int val) { + if (true) + {$$ + } + }; + +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInParenthesizedLambdaExpressionTest() + { + var markupBeforeCommit = +@"Func testForEquality = (x, y) => +{ + $$ + return x == y; +};"; + + var expectedCodeAfterCommit = +@"Func testForEquality = (x, y) => +{ + if (true) + {$$ + } + + return x == y; +};"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInSwitchExpression() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var operation = 2; + + var result = operation switch + { + $$ + 1 => ""Case 1"", + 2 => ""Case 2"", + 3 => ""Case 3"", + 4 => ""Case 4"", + }; + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInSingleLambdaExpression() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Func f = x => $$; + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInStringTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var str = ""$$""; + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInObjectInitializerTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var str = new Test($$); + } +} + +class Test +{ + private string val; + + public Test(string val) + { + this.val = val; + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInParameterListTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method(int x, $$) + { + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInRecordDeclarationTest() + { + var markupBeforeCommit = +@"public record Person +{ + $$ + public string FirstName { get; init; } = default!; + public string LastName { get; init; } = default!; +};"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInVariableDeclarationTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var x = $$ + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetWithInvocationBeforeAndAfterCursorTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Wr$$Blah + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + if (true) + {$$ + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetWithInvocationUnderscoreBeforeAndAfterCursorTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + _Wr$$Blah_ + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + if (true) + {$$ + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs b/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs index 545ff1750236c..dd6c20cc9ca9f 100644 --- a/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs +++ b/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs @@ -4,7 +4,9 @@ #nullable disable +using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.Editor.CSharp.DocumentationComments; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.DocumentationComments; using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; @@ -137,7 +139,10 @@ class C { }"; - VerifyTypingCharacter(code, expected, autoGenerateXmlDocComments: false); + VerifyTypingCharacter(code, expected, globalOptions: new OptionsCollection(LanguageNames.CSharp) + { + { DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, false } + }); } [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] @@ -785,7 +790,10 @@ class C { }"; - VerifyPressingEnter(code, expected, autoGenerateXmlDocComments: false); + VerifyPressingEnter(code, expected, globalOptions: new OptionsCollection(LanguageNames.CSharp) + { + { DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, false } + }); } [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] @@ -1344,7 +1352,10 @@ class C { }"; - VerifyPressingEnter(code, expected, autoGenerateXmlDocComments: false); + VerifyPressingEnter(code, expected, globalOptions: new OptionsCollection(LanguageNames.CSharp) + { + { DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, false } + }); } [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] @@ -1787,7 +1798,10 @@ class C { }"; - VerifyInsertCommentCommand(code, expected, autoGenerateXmlDocComments: false); + VerifyInsertCommentCommand(code, expected, globalOptions: new OptionsCollection(LanguageNames.CSharp) + { + { DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, false } + }); } [WorkItem(538714, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/538714")] @@ -2274,20 +2288,7 @@ class C { }"; /// class C { }"; - try - { - VerifyPressingEnter(code, expected, useTabs: true, setOptionsOpt: - workspace => - { - workspace.GetService().GlobalOptions - .SetOptionValue(DefaultOptions.TrimTrailingWhiteSpaceOptionName, true); - }); - } - finally - { - TestWorkspace.CreateCSharp("").GetService().GlobalOptions - .SetOptionValue(DefaultOptions.TrimTrailingWhiteSpaceOptionName, false); - } + VerifyPressingEnter(code, expected, useTabs: true, trimTrailingWhiteSpace: true); } [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index c374754a9ca8f..3f03e5a288fd3 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -10677,13 +10677,13 @@ public void InsertDeleteMethod_Inactive() new[] { DocumentResults( - activeStatements: GetActiveStatements(srcA1, srcA2, path: "0"), + activeStatements: GetActiveStatements(srcA1, srcA2, documentIndex: 0), semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F2")), }), DocumentResults( - activeStatements: GetActiveStatements(srcB1, srcB2, path: "1")) + activeStatements: GetActiveStatements(srcB1, srcB2, documentIndex: 1)) }); } @@ -10701,17 +10701,17 @@ public void InsertDeleteMethod_Active() var srcB2 = "partial class C { }"; EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] { GetTopEdits(srcA1, srcA2, documentIndex: 0), GetTopEdits(srcB1, srcB2, documentIndex: 1) }, new[] { DocumentResults( - activeStatements: GetActiveStatements(srcA1, srcA2, path: "0"), + activeStatements: GetActiveStatements(srcA1, srcA2, documentIndex: 0), semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), }), DocumentResults( - activeStatements: GetActiveStatements(srcB1, srcB2, path: "1"), + activeStatements: GetActiveStatements(srcB1, srcB2, documentIndex: 1), // TODO: this is odd AS location https://github.com/dotnet/roslyn/issues/54758 diagnostics: new[] { Diagnostic(RudeEditKind.DeleteActiveStatement, " partial c", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) }); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index b5ec229d031ff..8618ad852b15f 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -7,9 +7,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; +using EnvDTE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Differencing; @@ -32,6 +35,18 @@ public class CSharpEditAndContinueAnalyzerTests #region Helpers + private static TestWorkspace CreateWorkspace() + => new(composition: s_composition); + + private static Solution AddDefaultTestProject(Solution solution, string source) + { + var projectId = ProjectId.CreateNewId(); + + return solution. + AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), "proj", "proj", LanguageNames.CSharp)).GetProject(projectId). + AddDocument("test.cs", SourceText.From(source, Encoding.UTF8), filePath: Path.Combine(TempRoot.Root, "test.cs")).Project.Solution; + } + private static void TestSpans(string source, Func hasLabel) { var tree = SyntaxFactory.ParseSyntaxTree(source); @@ -192,7 +207,7 @@ public void ErrorSpans_TopLevel() } "; - TestSpans(source, node => SyntaxComparer.TopLevel.HasLabel(node)); + TestSpans(source, SyntaxComparer.TopLevel.HasLabel); } [Fact] @@ -248,7 +263,7 @@ void M() // TODO: test // /**/F($$from a in b from c in d select a.x);/**/ // /**/F(from a in b $$from c in d select a.x);/**/ - TestSpans(source, kind => SyntaxComparer.Statement.HasLabel(kind)); + TestSpans(source, SyntaxComparer.Statement.HasLabel); } /// @@ -257,8 +272,8 @@ void M() [Fact] public void ErrorSpansAllKinds() { - TestErrorSpansAllKinds(kind => SyntaxComparer.Statement.HasLabel(kind)); - TestErrorSpansAllKinds(kind => SyntaxComparer.TopLevel.HasLabel(kind)); + TestErrorSpansAllKinds(SyntaxComparer.Statement.HasLabel); + TestErrorSpansAllKinds(SyntaxComparer.TopLevel.HasLabel); } [Fact] @@ -283,14 +298,14 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - var oldSolution = workspace.CurrentSolution; + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var oldText = await oldDocument.GetTextAsync(); var oldSyntaxRoot = await oldDocument.GetSyntaxRootAsync(); var documentId = oldDocument.Id; - var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)); var newDocument = newSolution.GetDocument(documentId); var newText = await newDocument.GetTextAsync(); var newSyntaxRoot = await newDocument.GetSyntaxRootAsync(); @@ -350,12 +365,12 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - var oldSolution = workspace.CurrentSolution; + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; - var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)); var result = await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)); @@ -377,8 +392,9 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source, composition: s_composition); - var oldProject = workspace.CurrentSolution.Projects.Single(); + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source); + var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var result = await AnalyzeDocumentAsync(oldProject, oldDocument); @@ -410,14 +426,13 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - - var oldSolution = workspace.CurrentSolution; + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; - var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)); var result = await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)); @@ -440,11 +455,15 @@ public static void Main() "; var experimentalFeatures = new Dictionary(); // no experimental features to enable var experimental = TestOptions.Regular.WithFeatures(experimentalFeatures); + var root = SyntaxFactory.ParseCompilationUnit(source, options: experimental); - using var workspace = TestWorkspace.CreateCSharp( - source, parseOptions: experimental, compilationOptions: null, composition: s_composition); + using var workspace = CreateWorkspace(); + + var projectId = ProjectId.CreateNewId(); + var oldSolution = workspace.CurrentSolution. + AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), "proj", "proj", LanguageNames.CSharp)).GetProject(projectId). + AddDocument("test.cs", root, filePath: Path.Combine(TempRoot.Root, "test.cs")).Project.Solution; - var oldSolution = workspace.CurrentSolution; var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; @@ -520,9 +539,9 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source, composition: s_composition); + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source); - var oldSolution = workspace.CurrentSolution; var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; @@ -558,14 +577,13 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - - var oldSolution = workspace.CurrentSolution; + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; - var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)); var result = await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)); @@ -598,14 +616,13 @@ public static void Main(Bar x) } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - - var oldSolution = workspace.CurrentSolution; + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); var oldProject = oldSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; - var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)); var result = await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)); @@ -640,14 +657,13 @@ public class D } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); + // fork the solution to introduce a change - var oldProject = workspace.CurrentSolution.Projects.Single(); + var oldProject = oldSolution.Projects.Single(); var newDocId = DocumentId.CreateNewId(oldProject.Id); - var oldSolution = workspace.CurrentSolution; - var newSolution = oldSolution.AddDocument(newDocId, "goo.cs", SourceText.From(source2)); - - workspace.TryApplyChanges(newSolution); + var newSolution = oldSolution.AddDocument(newDocId, "goo.cs", SourceText.From(source2), filePath: Path.Combine(TempRoot.Root, "goo.cs")); var newProject = newSolution.Projects.Single(); var changes = newProject.GetChanges(oldProject); @@ -688,14 +704,12 @@ class D } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); - var oldSolution = workspace.CurrentSolution; var oldProject = oldSolution.Projects.Single(); var newDocId = DocumentId.CreateNewId(oldProject.Id); - var newSolution = oldSolution.AddDocument(newDocId, "goo.cs", SourceText.From(source2)); - - workspace.TryApplyChanges(newSolution); + var newSolution = oldSolution.AddDocument(newDocId, "goo.cs", SourceText.From(source2), filePath: Path.Combine(TempRoot.Root, "goo.cs")); var newProject = newSolution.Projects.Single(); var changes = newProject.GetChanges(oldProject); @@ -722,17 +736,17 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) var source1 = @"class C {}"; var source2 = @"class C { int x; }"; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - var oldProject = workspace.CurrentSolution.Projects.Single(); + var filePath = Path.Combine(TempRoot.Root, "src.cs"); + + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); + var oldProject = oldSolution.Projects.Single(); var documentId = DocumentId.CreateNewId(oldProject.Id); - var oldSolution = workspace.CurrentSolution; - var newSolution = oldSolution.AddDocument(documentId, "goo.cs", SourceText.From(source2), filePath: "src.cs"); + var newSolution = oldSolution.AddDocument(documentId, "goo.cs", SourceText.From(source2), filePath: filePath); var newProject = newSolution.Projects.Single(); var newDocument = newProject.GetDocument(documentId); var newSyntaxTree = await newDocument.GetSyntaxTreeAsync().ConfigureAwait(false); - workspace.TryApplyChanges(newSolution); - var baseActiveStatements = AsyncLazy.Create(ActiveStatementsMap.Empty); var capabilities = AsyncLazy.Create(EditAndContinueTestHelpers.Net5RuntimeCapabilities); @@ -747,10 +761,10 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, capabilities, CancellationToken.None); var expectedDiagnostic = outOfMemory ? - $"ENC0089: {string.Format(FeaturesResources.Modifying_source_file_0_requires_restarting_the_application_because_the_file_is_too_big, "src.cs")}" : + $"ENC0089: {string.Format(FeaturesResources.Modifying_source_file_0_requires_restarting_the_application_because_the_file_is_too_big, filePath)}" : // Because the error message that is formatted into this template string includes a stacktrace with newlines, we need to replicate that behavior // here so that any trailing punctuation is removed from the translated template string. - $"ENC0080: {string.Format(FeaturesResources.Modifying_source_file_0_requires_restarting_the_application_due_to_internal_error_1, "src.cs", "System.NullReferenceException: NullRef!\n")}".Split('\n').First(); + $"ENC0080: {string.Format(FeaturesResources.Modifying_source_file_0_requires_restarting_the_application_due_to_internal_error_1, filePath, "System.NullReferenceException: NullRef!\n")}".Split('\n').First(); AssertEx.Equal(new[] { expectedDiagnostic }, result.RudeEditErrors.Select(d => d.ToDiagnostic(newSyntaxTree)) .Select(d => $"{d.Id}: {d.GetMessage().Split(new[] { Environment.NewLine }, StringSplitOptions.None).First()}")); @@ -778,12 +792,12 @@ public static void Main() } "; - using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); + using var workspace = CreateWorkspace(); + var oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1); - var oldSolution = workspace.CurrentSolution; var oldProject = oldSolution.Projects.Single(); var documentId = oldProject.Documents.Single().Id; - var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)); var newDocument = newSolution.GetDocument(documentId); var result = await AnalyzeDocumentAsync(oldProject, newDocument, capabilities: EditAndContinueCapabilities.None); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs index e69d2782a50b4..cf2b28addd041 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs @@ -23,6 +23,7 @@ public CSharpEditAndContinueTestHelpers(Action? faultInjector = null public override AbstractEditAndContinueAnalyzer Analyzer => _analyzer; public override string LanguageName => LanguageNames.CSharp; + public override string ProjectFileExtension => ".csproj"; public override TreeComparer TopSyntaxComparer => SyntaxComparer.TopLevel; public override ImmutableArray GetDeclarators(ISymbol method) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 57fcddedcdaf2..8739a02a80cb5 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UnitTests; @@ -14,6 +15,7 @@ using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Xunit; @@ -73,11 +75,14 @@ internal static DocumentAnalysisResultsDescription DocumentResults( RudeEditDiagnosticDescription[]? diagnostics = null) => new(activeStatements, semanticEdits, lineEdits: null, diagnostics); + internal static string GetDocumentFilePath(int documentIndex) + => Path.Combine(TempRoot.Root, documentIndex.ToString() + ".cs"); + private static SyntaxTree ParseSource(string markedSource, int documentIndex = 0) => SyntaxFactory.ParseSyntaxTree( ActiveStatementsDescription.ClearTags(markedSource), CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview), - path: documentIndex.ToString()); + path: GetDocumentFilePath(documentIndex)); internal static EditScript GetTopEdits(string src1, string src2, int documentIndex = 0) { @@ -173,8 +178,8 @@ internal static string WrapMethodBodyWithClass(string bodySource, MethodKind kin _ => "class C { void F() { " + bodySource + " } }", }; - internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource, ActiveStatementFlags[] flags = null, string path = "0") - => new(oldSource, newSource, source => SyntaxFactory.ParseSyntaxTree(source, path: path), flags); + internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource, ActiveStatementFlags[] flags = null, int documentIndex = 0) + => new(oldSource, newSource, source => SyntaxFactory.ParseSyntaxTree(source, path: GetDocumentFilePath(documentIndex)), flags); internal static SyntaxMapDescription GetSyntaxMap(string oldSource, string newSource) => new(oldSource, newSource); diff --git a/src/EditorFeatures/CSharpTest/ExtractClass/ExtractClassTests.cs b/src/EditorFeatures/CSharpTest/ExtractClass/ExtractClassTests.cs index 2d99f75b04846..ce8e63444a650 100644 --- a/src/EditorFeatures/CSharpTest/ExtractClass/ExtractClassTests.cs +++ b/src/EditorFeatures/CSharpTest/ExtractClass/ExtractClassTests.cs @@ -538,6 +538,168 @@ class Test : MyBase }.RunAsync(); } + [Fact] + public async Task TestFieldSelectInKeywords() + { + var input = @" +class Test +{ + priva[||]te int MyField; +}"; + + var expected1 = @" +class Test : MyBase +{ +}"; + var expected2 = @"internal class MyBase +{ + private int MyField; +}"; + + await new Test + { + TestCode = input, + FixedState = + { + Sources = + { + expected1, + expected2 + } + } + }.RunAsync(); + } + + [Fact] + public async Task TestFieldSelectAfterSemicolon() + { + var input = @" +class Test +{ + private int MyField;[||] +}"; + + var expected1 = @" +class Test : MyBase +{ +}"; + var expected2 = @"internal class MyBase +{ + private int MyField; +}"; + + await new Test + { + TestCode = input, + FixedState = + { + Sources = + { + expected1, + expected2 + } + } + }.RunAsync(); + } + + [Fact] + public async Task TestFieldSelectEntireDeclaration() + { + var input = @" +class Test +{ + [|private int MyField;|] +}"; + + var expected1 = @" +class Test : MyBase +{ +}"; + var expected2 = @"internal class MyBase +{ + private int MyField; +}"; + + await new Test + { + TestCode = input, + FixedState = + { + Sources = + { + expected1, + expected2 + } + } + }.RunAsync(); + } + + [Fact] + public async Task TestFieldSelectMultipleVariables1() + { + var input = @" +class Test +{ + [|private int MyField1, MyField2;|] +}"; + + var expected1 = @" +class Test : MyBase +{ +}"; + var expected2 = @"internal class MyBase +{ + private int MyField1; + private int MyField2; +}"; + + await new Test + { + TestCode = input, + FixedState = + { + Sources = + { + expected1, + expected2 + } + } + }.RunAsync(); + } + + [Fact] + public async Task TestFieldSelectMultipleVariables2() + { + var input = @" +class Test +{ + private int MyField1, [|MyField2;|] +}"; + + var expected1 = @" +class Test : MyBase +{ + private int MyField1; +}"; + var expected2 = @"internal class MyBase +{ + private int MyField2; +}"; + + await new Test + { + TestCode = input, + FixedState = + { + Sources = + { + expected1, + expected2 + } + } + }.RunAsync(); + } + [Fact] public async Task TestFileHeader_FromExistingFile() { @@ -2090,7 +2252,7 @@ public TestExtractClassOptionsService(IEnumerable<(string name, bool makeAbstrac public string FileName { get; set; } = "MyBase.cs"; public string BaseName { get; set; } = "MyBase"; - public Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalSymbol, ISymbol? selectedMember, CancellationToken cancellationToken) + public Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalSymbol, ImmutableArray selectedMembers, CancellationToken cancellationToken) { var availableMembers = originalSymbol.GetMembers().Where(member => MemberAndDestinationValidator.IsMemberValid(member)); @@ -2098,7 +2260,7 @@ public TestExtractClassOptionsService(IEnumerable<(string name, bool makeAbstrac if (_dialogSelection == null) { - if (selectedMember is null) + if (selectedMembers.IsEmpty) { Assert.True(isClassDeclarationSelection); selections = availableMembers.Select(member => (member, makeAbstract: false)); @@ -2106,7 +2268,7 @@ public TestExtractClassOptionsService(IEnumerable<(string name, bool makeAbstrac else { Assert.False(isClassDeclarationSelection); - selections = new[] { (selectedMember, false) }; + selections = selectedMembers.Select(m => (m, makeAbstract: false)); } } else diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs index 2f7422c142852..b6f76845174b2 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs @@ -20,7 +20,9 @@ using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; +using Newtonsoft.Json.Linq; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -2616,13 +2618,16 @@ private static void AssertFormatAfterTypeChar(string code, string expected, Opti { using var workspace = TestWorkspace.CreateCSharp(code, parseOptions: parseOptions); - globalOptions?.SetGlobalOptions(workspace.GlobalOptions); - var subjectDocument = workspace.Documents.Single(); var commandHandler = workspace.GetService(); var typedChar = subjectDocument.GetTextBuffer().CurrentSnapshot.GetText(subjectDocument.CursorPosition.Value - 1, 1); - commandHandler.ExecuteCommand(new TypeCharCommandArgs(subjectDocument.GetTextView(), subjectDocument.GetTextBuffer(), typedChar[0]), () => { }, TestCommandExecutionContext.Create()); + var textView = subjectDocument.GetTextView(); + + globalOptions?.SetGlobalOptions(workspace.GlobalOptions); + workspace.GlobalOptions.SetEditorOptions(textView.Options.GlobalOptions, subjectDocument.Project.Language); + + commandHandler.ExecuteCommand(new TypeCharCommandArgs(textView, subjectDocument.GetTextBuffer(), typedChar[0]), () => { }, TestCommandExecutionContext.Create()); var newSnapshot = subjectDocument.GetTextBuffer().CurrentSnapshot; diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs index 006ee56d8d55c..b4050f4f7c496 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -3522,11 +3523,6 @@ private static void AssertSmartIndentInProjection( { using var workspace = TestWorkspace.CreateCSharp(markup, parseOptions: option, composition: s_compositionWithTestFormattingRules); - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options - .WithChangedOption(UseTabs, LanguageNames.CSharp, useTabs))); - - workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp), indentStyle); - var subjectDocument = workspace.Documents.Single(); var projectedDocument = @@ -3536,12 +3532,23 @@ private static void AssertSmartIndentInProjection( provider.BaseIndentation = BaseIndentationOfNugget; provider.TextSpan = subjectDocument.SelectedSpans.Single(); + var editorOptionsService = workspace.GetService(); + var indentationLine = projectedDocument.GetTextBuffer().CurrentSnapshot.GetLineFromPosition(projectedDocument.CursorPosition.Value); - var point = projectedDocument.GetTextView().BufferGraph.MapDownToBuffer(indentationLine.Start, PointTrackingMode.Negative, subjectDocument.GetTextBuffer(), PositionAffinity.Predecessor); + var textView = projectedDocument.GetTextView(); + var buffer = subjectDocument.GetTextBuffer(); + var point = textView.BufferGraph.MapDownToBuffer(indentationLine.Start, PointTrackingMode.Negative, buffer, PositionAffinity.Predecessor); + + var editorOptions = editorOptionsService.Factory.GetOptions(buffer); + editorOptions.SetOptionValue(DefaultOptions.IndentStyleId, indentStyle.ToEditorIndentStyle()); + editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); TestIndentation( - point.Value, expectedIndentation, - projectedDocument.GetTextView(), subjectDocument, workspace.GlobalOptions); + point.Value, + expectedIndentation, + textView, + subjectDocument, + editorOptionsService); } } diff --git a/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateConstructorFromMembers/GenerateConstructorFromMembersTests.cs b/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateConstructorFromMembers/GenerateConstructorFromMembersTests.cs index f8c888d8a08e2..9a9813c242b87 100644 --- a/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateConstructorFromMembers/GenerateConstructorFromMembersTests.cs +++ b/src/EditorFeatures/CSharpTest/GenerateFromMembers/GenerateConstructorFromMembers/GenerateConstructorFromMembersTests.cs @@ -798,6 +798,49 @@ public Program(int field{|Navigation:)|} }"); } + [WorkItem(62162, "https://github.com/dotnet/roslyn/issues/62162")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructorFromMembers)] + public async Task TestUnderscoreInName_KeepIfNameWithoutUnderscoreIsInvalid() + { + await TestInRegularAndScriptAsync( +@"class Program +{ + [|int _0;|] +}", +@"class Program +{ + int _0; + + public Program(int _0{|Navigation:)|} + { + this._0 = _0; + } +}"); + } + + [WorkItem(62162, "https://github.com/dotnet/roslyn/issues/62162")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructorFromMembers)] + [InlineData('m')] + [InlineData('s')] + [InlineData('t')] + public async Task TestCommonPatternInName_KeepUnderscoreIfNameWithoutItIsInvalid(char commonPatternChar) + { + await TestInRegularAndScriptAsync( +$@"class Program +{{ + [|int {commonPatternChar}_0;|] +}}", +$@"class Program +{{ + int {commonPatternChar}_0; + + public Program(int _0{{|Navigation:)|}} + {{ + {commonPatternChar}_0 = _0; + }} +}}"); + } + [WorkItem(14219, "https://github.com/dotnet/roslyn/issues/14219")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructorFromMembers)] public async Task TestUnderscoreInName_PreferThis() diff --git a/src/EditorFeatures/CSharpTest/MoveStaticMembers/CSharpMoveStaticMembersTests.cs b/src/EditorFeatures/CSharpTest/MoveStaticMembers/CSharpMoveStaticMembersTests.cs index 06611eeb08b5f..389171ea702df 100644 --- a/src/EditorFeatures/CSharpTest/MoveStaticMembers/CSharpMoveStaticMembersTests.cs +++ b/src/EditorFeatures/CSharpTest/MoveStaticMembers/CSharpMoveStaticMembersTests.cs @@ -19,7 +19,7 @@ public class CSharpMoveStaticMembersTests { private static readonly TestComposition s_testServices = FeaturesTestCompositions.Features.AddParts(typeof(TestMoveStaticMembersService)); - #region Perform Actions From Options + #region Perform New Type Action From Options [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] public async Task TestMoveField() { @@ -2193,6 +2193,433 @@ public static int TestMethod() } #endregion + #region Perform Existing Type Action From Options + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveFieldToExistingType() + { + var initialSourceMarkup = @" +public class Class1 +{ + public static int Test[||]Field = 1; +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestField"); + var fixedSourceMarkup = @" +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +public class Class1Helpers +{ + public static int TestField = 1; +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMovePropertyToExistingType() + { + var initialSourceMarkup = @" +public class Class1 +{ + public static int Test[||]Property { get; set; } +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestProperty"); + var fixedSourceMarkup = @" +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +public class Class1Helpers +{ + public static int TestProperty { get; set; } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveEventToExistingType() + { + var initialSourceMarkup = @" +using System; + +public class Class1 +{ + public static event EventHandler Test[||]Event; +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestEvent"); + var fixedSourceMarkup = @" +using System; + +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +using System; + +public class Class1Helpers +{ + public static event EventHandler TestEvent; +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveMethodToExistingType() + { + var initialSourceMarkup = @" +public class Class1 +{ + public static int Test[||]Method() + { + return 0; + } +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestMethod"); + var fixedSourceMarkup = @" +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +public class Class1Helpers +{ + public static int TestMethod() + { + return 0; + } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveExtensionMethodToExistingType() + { + var initialSourceMarkup = @" +public static class Class1 +{ + public static int Test[||]Method(this Other other) + { + return other.OtherInt + 2; + } +} + +public class Other +{ + public int OtherInt; + public Other() + { + OtherInt = 5; + } +}"; + var initialDestinationMarkup = @" +public static class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestMethod"); + var fixedSourceMarkup = @" +public static class Class1 +{ +} + +public class Other +{ + public int OtherInt; + public Other() + { + OtherInt = 5; + } +}"; + var fixedDestinationMarkup = @" +public static class Class1Helpers +{ + public static int TestMethod(this Other other) + { + return other.OtherInt + 2; + } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveConstFieldToExistingType() + { + var initialSourceMarkup = @" +public class Class1 +{ + public const int Test[||]Field = 1; +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestField"); + var fixedSourceMarkup = @" +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +public class Class1Helpers +{ + public const int TestField = 1; +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveMethodToExistingTypeWithNamespace() + { + var initialSourceMarkup = @" +namespace TestNs +{ + public class Class1 + { + public static int Test[||]Method() + { + return 0; + } + } +}"; + var initialDestinationMarkup = @" +namespace TestNs +{ + public class Class1Helpers + { + } +}"; + var selectedDestinationName = "TestNs.Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestMethod"); + var fixedSourceMarkup = @" +namespace TestNs +{ + public class Class1 + { + } +}"; + var fixedDestinationMarkup = @" +namespace TestNs +{ + public class Class1Helpers + { + public static int TestMethod() + { + return 0; + } + } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveMethodToExistingTypeWithNewNamespace() + { + var initialSourceMarkup = @" +public class Class1 +{ + public static int Test[||]Method() + { + return 0; + } +}"; + var initialDestinationMarkup = @" +namespace TestNs +{ + public class Class1Helpers + { + } +}"; + var selectedDestinationName = "TestNs.Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestMethod"); + var fixedSourceMarkup = @" +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +namespace TestNs +{ + public class Class1Helpers + { + public static int TestMethod() + { + return 0; + } + } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveMethodToExistingTypeRefactorSourceUsage() + { + var initialSourceMarkup = @" +public class Class1 +{ + public static int Test[||]Method() + { + return 0; + } + + public static int TestMethod2() + { + return TestMethod(); + } +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestMethod"); + var fixedSourceMarkup = @" +public class Class1 +{ + public static int TestMethod2() + { + return Class1Helpers.TestMethod(); + } +}"; + var fixedDestinationMarkup = @" +public class Class1Helpers +{ + public static int TestMethod() + { + return 0; + } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestMoveMethodToExistingTypeRefactorDestinationUsage() + { + var initialSourceMarkup = @" +public class Class1 +{ + public static int Test[||]Method() + { + return 0; + } +}"; + var initialDestinationMarkup = @" +public class Class1Helpers +{ + public static int TestMethod2() + { + return Class1.TestMethod(); + } +}"; + var selectedDestinationName = "Class1Helpers"; + var selectedMembers = ImmutableArray.Create("TestMethod"); + var fixedSourceMarkup = @" +public class Class1 +{ +}"; + var fixedDestinationMarkup = @" +public class Class1Helpers +{ + public static int TestMethod() + { + return 0; + } + public static int TestMethod2() + { + return Class1Helpers.TestMethod(); + } +}"; + + await TestMovementExistingFileAsync( + initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + selectedMembers, + selectedDestinationName).ConfigureAwait(false); + } + #endregion + #region Selections and caret position [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] @@ -2426,6 +2853,245 @@ internal static class Class1Helpers await TestMovementNewFileAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestSelectInMultipleFieldIdentifiers() + { + var initialMarkup = @" +namespace TestNs1 +{ + public class Class1 + { + [|public static int Goo = 10, Foo = 9;|] + } +}"; + var selectedDestinationName = "Class1Helpers"; + var newFileName = "Class1Helpers.cs"; + var selectedMembers = ImmutableArray.Create("Goo", "Foo"); + var expectedResult1 = @" +namespace TestNs1 +{ + public class Class1 + { + } +}"; + var expectedResult2 = @"namespace TestNs1 +{ + internal static class Class1Helpers + { + public static int Goo = 10; + public static int Foo = 9; + } +}"; + + await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestSelectMultipleMembers1() + { + var initialMarkup = @" +namespace TestNs1 +{ + public class Class1 + { + [|public static int Goo = 10, Foo = 9; + + public static int DoSomething() + { + return 5; + }|] + } +}"; + var selectedDestinationName = "Class1Helpers"; + var newFileName = "Class1Helpers.cs"; + var selectedMembers = ImmutableArray.Create("Goo", "Foo", "DoSomething"); + var expectedResult1 = @" +namespace TestNs1 +{ + public class Class1 + { + } +}"; + var expectedResult2 = @"namespace TestNs1 +{ + internal static class Class1Helpers + { + public static int Goo = 10; + public static int Foo = 9; + + public static int DoSomething() + { + return 5; + } + } +}"; + + await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestSelectMultipleMembers2() + { + var initialMarkup = @" +namespace TestNs1 +{ + public class Class1 + { + + public static int DoSomething() + { + return [|5; + } + public static int Goo = 10, Foo = 9;|] + } +}"; + var selectedDestinationName = "Class1Helpers"; + var newFileName = "Class1Helpers.cs"; + var selectedMembers = ImmutableArray.Create("Goo", "Foo"); + var expectedResult1 = @" +namespace TestNs1 +{ + public class Class1 + { + + public static int DoSomething() + { + return 5; + } + } +}"; + var expectedResult2 = @"namespace TestNs1 +{ + internal static class Class1Helpers + { + public static int Goo = 10; + public static int Foo = 9; + } +}"; + + await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestSelectMultipleMembers3() + { + var initialMarkup = @" +namespace TestNs1 +{ + public class Class1 + { + public static int Go[|o = 10, Foo = 9; + + public static int DoSometh|]ing() + { + return 5; + } + } +}"; + var selectedDestinationName = "Class1Helpers"; + var newFileName = "Class1Helpers.cs"; + var selectedMembers = ImmutableArray.Create("Goo", "Foo", "DoSomething"); + var expectedResult1 = @" +namespace TestNs1 +{ + public class Class1 + { + } +}"; + var expectedResult2 = @"namespace TestNs1 +{ + internal static class Class1Helpers + { + public static int Goo = 10; + public static int Foo = 9; + + public static int DoSomething() + { + return 5; + } + } +}"; + + await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestSelectMultipleMembers4() + { + var initialMarkup = @" +namespace TestNs1 +{ + public class Class1 + { + public static int Goo = 10, F[|oo = 9; + + public static in|]t DoSomething() + { + return 5; + } + } +}"; + var selectedDestinationName = "Class1Helpers"; + var newFileName = "Class1Helpers.cs"; + var selectedMembers = ImmutableArray.Create("Foo"); + var expectedResult1 = @" +namespace TestNs1 +{ + public class Class1 + { + public static int Goo = 10; + + public static int DoSomething() + { + return 5; + } + } +}"; + var expectedResult2 = @"namespace TestNs1 +{ + internal static class Class1Helpers + { + public static int Foo = 9; + } +}"; + + await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] + public async Task TestSelectOneOfMultipleFieldIdentifiers() + { + // However, a semicolon after the initializer is still considered a declaration + var initialMarkup = @" +namespace TestNs1 +{ + public class Class1 + { + public static int G[||]oo = 10, Foo = 9; + } +}"; + var selectedDestinationName = "Class1Helpers"; + var newFileName = "Class1Helpers.cs"; + var selectedMembers = ImmutableArray.Create("Goo"); + var expectedResult1 = @" +namespace TestNs1 +{ + public class Class1 + { + public static int Foo = 9; + } +}"; + var expectedResult2 = @"namespace TestNs1 +{ + internal static class Class1Helpers + { + public static int Goo = 10; + } +}"; + + await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedResult1, expectedResult2, newFileName, selectedMembers, selectedDestinationName).ConfigureAwait(false); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] public async Task TestSelectInTypeIdentifierOfFieldDeclaration_NoAction() { @@ -2567,54 +3233,43 @@ public class Class1 } #endregion - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveStaticMembers)] - public async Task NoOptionsService_NoAction() - { - var initialMarkup = @" -namespace TestNs1 -{ - public class Class1 - { - public static int TestField = 1;[||] - } -}"; - await TestNoRefactoringAsync(initialMarkup, hostServices: FeaturesTestCompositions.Features.GetHostServices()).ConfigureAwait(false); - } - private class Test : VerifyCS.Test { public Test( string destinationType, ImmutableArray selection, - string destinationName = "a.cs", - HostServices? hostServices = null) + string? destinationName, + bool testPreselection = false, + bool createNew = true) { _destinationType = destinationType; _selection = selection; _destinationName = destinationName; - _hostServices = hostServices; + _testPreselection = testPreselection; + _createNew = createNew; } private readonly string _destinationType; private readonly ImmutableArray _selection; - private readonly string _destinationName; + private readonly string? _destinationName; - private readonly HostServices? _hostServices; + private readonly bool _createNew; + + private readonly bool _testPreselection; protected override Workspace CreateWorkspaceImpl() { - var hostServices = _hostServices ?? s_testServices.GetHostServices(); + var hostServices = s_testServices.GetHostServices(); var workspace = new AdhocWorkspace(hostServices); - var testOptionsService = workspace.Services.GetService() as TestMoveStaticMembersService; - if (testOptionsService is not null) - { - testOptionsService.DestinationType = _destinationType; - testOptionsService.SelectedMembers = _selection; - testOptionsService.Filename = _destinationName; - } + var testOptionsService = (TestMoveStaticMembersService)workspace.Services.GetRequiredService(); + testOptionsService.DestinationName = _destinationType; + testOptionsService.SelectedMembers = _selection; + testOptionsService.Filename = _destinationName; + testOptionsService.CreateNew = _createNew; + testOptionsService.ExpectedPrecheckedMembers = _testPreselection ? _selection : ImmutableArray.Empty; return workspace; } @@ -2640,9 +3295,55 @@ private static async Task TestMovementNewFileAsync( }, }.RunAsync().ConfigureAwait(false); - private static async Task TestNoRefactoringAsync(string initialMarkup, HostServices? hostServices = null) + private static async Task TestMovementNewFileWithSelectionAsync( + string initialMarkup, + string expectedSource, + string expectedNewFile, + string newFileName, + ImmutableArray selectedMembers, + string newTypeName) + => await new Test(newTypeName, selectedMembers, newFileName, testPreselection: true) + { + TestCode = initialMarkup, + FixedState = + { + Sources = + { + expectedSource, + (newFileName, expectedNewFile) + } + }, + }.RunAsync().ConfigureAwait(false); + + private static async Task TestMovementExistingFileAsync( + string intialSourceMarkup, + string initialDestinationMarkup, + string fixedSourceMarkup, + string fixedDestinationMarkup, + ImmutableArray selectedMembers, + string selectedDestinationType, + string? selectedDestinationFile = null) + { + var test = new Test(selectedDestinationType, selectedMembers, selectedDestinationFile, createNew: false); + test.TestState.Sources.Add(intialSourceMarkup); + test.FixedState.Sources.Add(fixedSourceMarkup); + if (selectedDestinationFile != null) + { + test.TestState.Sources.Add((selectedDestinationFile, initialDestinationMarkup)); + test.FixedState.Sources.Add((selectedDestinationFile, fixedDestinationMarkup)); + } + else + { + test.TestState.Sources.Add(initialDestinationMarkup); + test.FixedState.Sources.Add(fixedDestinationMarkup); + } + + await test.RunAsync().ConfigureAwait(false); + } + + private static async Task TestNoRefactoringAsync(string initialMarkup) { - await new Test("", ImmutableArray.Empty, hostServices: hostServices) + await new Test("", ImmutableArray.Empty, "") { TestCode = initialMarkup, FixedCode = initialMarkup, diff --git a/src/EditorFeatures/CSharpTest/PullMemberUp/CSharpPullMemberUpTests.cs b/src/EditorFeatures/CSharpTest/PullMemberUp/CSharpPullMemberUpTests.cs index 9e299784c158f..3832ec9942161 100644 --- a/src/EditorFeatures/CSharpTest/PullMemberUp/CSharpPullMemberUpTests.cs +++ b/src/EditorFeatures/CSharpTest/PullMemberUp/CSharpPullMemberUpTests.cs @@ -5897,6 +5897,318 @@ public class B : A await TestWithPullMemberDialogAsync(testText, expected); } + [Fact] + public async Task TestRefactoringSelectionFieldKeyword1_NoAction() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + pub[|l|]ic int Goo = 10; +}"; + await TestQuickActionNotProvidedAsync(text); + } + + [Fact] + public async Task TestRefactoringSelectionFieldKeyword2() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + pub[||]lic int Goo = 10; +}"; + var expected = @" +public class BaseClass +{ + public int Goo = 10; +} + +public class Bar : BaseClass +{ +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionFieldAfterSemicolon() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + public int Goo = 10;[||] +}"; + var expected = @" +public class BaseClass +{ + public int Goo = 10; +} + +public class Bar : BaseClass +{ +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionFieldEntireDeclaration() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + [|public int Goo = 10;|] +}"; + var expected = @" +public class BaseClass +{ + public int Goo = 10; +} + +public class Bar : BaseClass +{ +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionMultipleFieldsInDeclaration1() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + [|public int Goo = 10, Foo = 9;|] +}"; + var expected = @" +public class BaseClass +{ + public int Goo = 10; + public int Foo = 9; +} + +public class Bar : BaseClass +{ +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionMultipleFieldsInDeclaration2() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + public int Go[||]o = 10, Foo = 9; +}"; + var expected = @" +public class BaseClass +{ + public int Goo = 10; +} + +public class Bar : BaseClass +{ + public int Foo = 9; +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionMultipleFieldsInDeclaration3() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + public int Goo = 10, [||]Foo = 9; +}"; + var expected = @" +public class BaseClass +{ + public int Foo = 9; +} + +public class Bar : BaseClass +{ + public int Goo = 10; +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionMultipleMembers1() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + [|public int Goo = 10, Foo = 9; + + public int DoSomething() + { + return 5; + }|] +}"; + var expected = @" +public class BaseClass +{ + public int Goo = 10; + public int Foo = 9; + + public int DoSomething() + { + return 5; + } +} + +public class Bar : BaseClass +{ +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + // Some of these have weird whitespace spacing that might suggest a bug + [Fact] + public async Task TestRefactoringSelectionMultipleMembers2() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + public int DoSomething() + { + [|return 5; + } + + + public int Goo = 10, Foo = 9;|] +}"; + var expected = @" +public class BaseClass +{ + + + public int Goo = 10; + + + public int Foo = 9; +} + +public class Bar : BaseClass +{ + public int DoSomething() + { + return 5; + } +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionMultipleMembers3() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + public int DoSom[|ething() + { + return 5; + } + + + public int Go|]o = 10, Foo = 9; +}"; + var expected = @" +public class BaseClass +{ + + + public int Goo = 10; + public int DoSomething() + { + return 5; + } +} + +public class Bar : BaseClass +{ + public int Foo = 9; +}"; + await TestWithPullMemberDialogAsync(text, expected); + } + + [Fact] + public async Task TestRefactoringSelectionMultipleMembers4() + { + var text = @" +public class BaseClass +{ +} + +public class Bar : BaseClass +{ + public int DoSomething()[| + { + return 5; + } + + + public int Goo = 10, F|]oo = 9; +}"; + var expected = @" +public class BaseClass +{ + + + public int Goo = 10; + + + public int Foo = 9; +} + +public class Bar : BaseClass +{ + public int DoSomething() + { + return 5; + } +}"; + await TestWithPullMemberDialogAsync(text, expected); + } #endregion } } diff --git a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs index 79a8f167985e2..1f7cde12dcfe7 100644 --- a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs @@ -49,21 +49,26 @@ private static void TestWorker( { using var workspace = TestWorkspace.CreateCSharp(inputMarkup); - // TODO: set SmartIndent to textView.Options (https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1412138) - workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp), indentStyle); - if (useTabs && expectedOutputMarkup != null) { Assert.Contains("\t", expectedOutputMarkup); } + var editorOptionsFactory = workspace.GetService(); + var document = workspace.Documents.Single(); var view = document.GetTextView(); + var textBuffer = view.TextBuffer; + var options = editorOptionsFactory.GetOptions(textBuffer); + + options.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); + options.SetOptionValue(DefaultOptions.TabSizeOptionId, 4); + options.SetOptionValue(DefaultOptions.IndentStyleId, indentStyle.ToEditorIndentStyle()); - view.Options.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); - view.Options.SetOptionValue(DefaultOptions.TabSizeOptionId, 4); + // Remove once https://github.com/dotnet/roslyn/issues/62204 is fixed: + workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, document.Project.Language), indentStyle); - var originalSnapshot = view.TextBuffer.CurrentSnapshot; + var originalSnapshot = textBuffer.CurrentSnapshot; var originalSelections = document.SelectedSpans; var snapshotSpans = new List(); @@ -77,7 +82,7 @@ private static void TestWorker( var undoHistoryRegistry = workspace.GetService(); var commandHandler = workspace.ExportProvider.GetCommandHandler(nameof(SplitStringLiteralCommandHandler)); - if (!commandHandler.ExecuteCommand(new ReturnKeyCommandArgs(view, view.TextBuffer), TestCommandExecutionContext.Create())) + if (!commandHandler.ExecuteCommand(new ReturnKeyCommandArgs(view, textBuffer), TestCommandExecutionContext.Create())) { callback(); } @@ -87,7 +92,7 @@ private static void TestWorker( MarkupTestFile.GetSpans(expectedOutputMarkup, out var expectedOutput, out ImmutableArray expectedSpans); - Assert.Equal(expectedOutput, view.TextBuffer.CurrentSnapshot.AsText().ToString()); + Assert.Equal(expectedOutput, textBuffer.CurrentSnapshot.AsText().ToString()); Assert.Equal(expectedSpans.First().Start, view.Caret.Position.BufferPosition.Position); if (verifyUndo) diff --git a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs index 036aecc1b8656..11acb20fafeb1 100644 --- a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs +++ b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs @@ -239,6 +239,98 @@ public void M(List list) ResolveAndVerifySymbolList(members1, members2, comp1); } + [Fact] + public void FileType1() + { + var src1 = @"using System; + +namespace N1.N2 +{ + file class C { } +} +"; + var originalComp = CreateCompilation(src1, assemblyName: "Test"); + var newComp = CreateCompilation(src1, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(3, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + + [Fact] + public void FileType2() + { + var src1 = @"using System; + +namespace N1.N2 +{ + file class C { } +} +"; + var originalComp = CreateCompilation(src1, assemblyName: "Test"); + var newComp = CreateCompilation(src1, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(3, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + + [Fact] + public void FileType3() + { + var src1 = @"using System; + +namespace N1.N2 +{ + file class C { } +} +"; + // this should result in two entirely separate file symbols. + // note that the IDE can only distinguish file type symbols with the same name when they have distinct file paths. + // We are OK with this as we will require file types with identical names to have distinct file paths later in the preview. + // See https://github.com/dotnet/roslyn/issues/61999 + var originalComp = CreateCompilation(new[] { SyntaxFactory.ParseSyntaxTree(src1, path: "file1.cs"), SyntaxFactory.ParseSyntaxTree(src1, path: "file2.cs") }, assemblyName: "Test"); + var newComp = CreateCompilation(new[] { SyntaxFactory.ParseSyntaxTree(src1, path: "file1.cs"), SyntaxFactory.ParseSyntaxTree(src1, path: "file2.cs") }, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(4, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + + [Fact] + public void FileType4() + { + // we should be able to distinguish a file type and non-file type when they have the same source name. + var src1 = SyntaxFactory.ParseSyntaxTree(@"using System; + +namespace N1.N2 +{ + file class C { } +} +", path: "File1.cs"); + + var src2 = SyntaxFactory.ParseSyntaxTree(@" +namespace N1.N2 +{ + class C { } +} +", path: "File2.cs"); + var originalComp = CreateCompilation(new[] { src1, src2 }, assemblyName: "Test"); + var newComp = CreateCompilation(new[] { src1, src2 }, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(4, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + #endregion #region "Change to symbol" diff --git a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTestBase.cs b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTestBase.cs index 3c22b088c282f..deee173ac252a 100644 --- a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTestBase.cs +++ b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTestBase.cs @@ -234,17 +234,11 @@ private static void GetSourceMemberSymbols(INamespaceOrTypeSymbol symbol, List(), list); - } + localDumper?.GetLocalSymbols(memberSymbol.GetSymbol(), list); break; } diff --git a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs index c14e7076089f5..0acc20355cab7 100644 --- a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs +++ b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs @@ -19,6 +19,111 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SymbolId [UseExportProvider] public class SymbolKeyTests { + [Fact] + public async Task FileType_01() + { + var typeSource = @" +file class C1 +{ + public static void M() { } +} +"; + + var workspaceXml = @$" + + + + +{typeSource} + + + +"; + using var workspace = TestWorkspace.Create(workspaceXml); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(); + + var compilation = await project.GetCompilationAsync(); + var type = compilation.GetTypeByMetadataName("F0__C1"); + Assert.NotNull(type); + var symbolKey = SymbolKey.Create(type); + var resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + } + + [Fact] + public async Task FileType_02() + { + var workspaceXml = $$""" + + + + +file class C +{ + public static void M() { } +} + + +file class C +{ + public static void M() { } +} + + + +"""; + using var workspace = TestWorkspace.Create(workspaceXml); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(); + + var compilation = await project.GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName("F1__C"); + Assert.NotNull(type); + var symbolKey = SymbolKey.Create(type); + var resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + + type = compilation.GetTypeByMetadataName("F0__C"); + Assert.NotNull(type); + symbolKey = SymbolKey.Create(type); + resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + } + + [Fact] + public async Task FileType_03() + { + var workspaceXml = $$""" + + + + +file class C +{ + public class Inner { } +} + + + +"""; + using var workspace = TestWorkspace.Create(workspaceXml); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(); + + var compilation = await project.GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName("F0__C+Inner"); + Assert.NotNull(type); + var symbolKey = SymbolKey.Create(type); + var resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + } + [Fact, WorkItem(45437, "https://github.com/dotnet/roslyn/issues/45437")] public async Task TestGenericsAndNullability() { diff --git a/src/EditorFeatures/Core.Cocoa/Preview/PreviewFactoryService.cs b/src/EditorFeatures/Core.Cocoa/Preview/PreviewFactoryService.cs index eba52723824e3..7c9f59bacfbad 100644 --- a/src/EditorFeatures/Core.Cocoa/Preview/PreviewFactoryService.cs +++ b/src/EditorFeatures/Core.Cocoa/Preview/PreviewFactoryService.cs @@ -31,21 +31,19 @@ public PreviewFactoryService( IContentTypeRegistryService contentTypeRegistryService, IProjectionBufferFactoryService projectionBufferFactoryService, ICocoaTextEditorFactoryService textEditorFactoryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, ITextDifferencingSelectorService differenceSelectorService, IDifferenceBufferFactoryService differenceBufferService, - ICocoaDifferenceViewerFactoryService differenceViewerService, - IGlobalOptionService globalOptions) + ICocoaDifferenceViewerFactoryService differenceViewerService) : base(threadingContext, textBufferFactoryService, contentTypeRegistryService, projectionBufferFactoryService, - editorOptionsFactoryService, + editorOptionsService, differenceSelectorService, differenceBufferService, textEditorFactoryService.CreateTextViewRoleSet( - TextViewRoles.PreviewRole, PredefinedTextViewRoles.Analyzable), - globalOptions) + TextViewRoles.PreviewRole, PredefinedTextViewRoles.Analyzable)) { _differenceViewerService = differenceViewerService; } diff --git a/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetCommandHandler.cs index ca21831a27d28..998304787e01e 100644 --- a/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetCommandHandler.cs @@ -32,7 +32,7 @@ internal abstract class AbstractSnippetCommandHandler : protected readonly IThreadingContext ThreadingContext; protected readonly IExpansionServiceProvider ExpansionServiceProvider; protected readonly IExpansionManager ExpansionManager; - protected readonly IGlobalOptionService GlobalOptions; + protected readonly EditorOptionsService EditorOptionsService; public string DisplayName => FeaturesResources.Snippets; @@ -40,12 +40,12 @@ public AbstractSnippetCommandHandler( IThreadingContext threadingContext, IExpansionServiceProvider expansionServiceProvider, IExpansionManager expansionManager, - IGlobalOptionService globalOptions) + EditorOptionsService editorOptionsService) { ThreadingContext = threadingContext; ExpansionServiceProvider = expansionServiceProvider; ExpansionManager = expansionManager; - GlobalOptions = globalOptions; + EditorOptionsService = editorOptionsService; } protected abstract AbstractSnippetExpansionClient GetSnippetExpansionClient(ITextView textView, ITextBuffer subjectBuffer); @@ -290,7 +290,7 @@ protected bool TryHandleTypedSnippet(ITextView textView, ITextBuffer subjectBuff protected bool AreSnippetsEnabled(EditorCommandArgs args) { - return GlobalOptions.GetOption(InternalFeatureOnOffOptions.Snippets) && + return EditorOptionsService.GlobalOptions.GetOption(InternalFeatureOnOffOptions.Snippets) && // TODO (https://github.com/dotnet/roslyn/issues/5107): enable in interactive !(Workspace.TryGetWorkspace(args.SubjectBuffer.AsTextContainer(), out var workspace) && workspace.Kind == WorkspaceKind.Interactive); } diff --git a/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetExpansionClient.cs b/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetExpansionClient.cs index 49b11b7cca634..9a6a6ab08c92b 100644 --- a/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/EditorFeatures/Core.Cocoa/Snippets/AbstractSnippetExpansionClient.cs @@ -8,9 +8,11 @@ using System.Linq; using System.Threading; using System.Xml.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -30,8 +32,7 @@ internal abstract class AbstractSnippetExpansionClient : IExpansionClient protected readonly IContentType LanguageServiceGuid; protected readonly ITextView TextView; protected readonly ITextBuffer SubjectBuffer; - - public readonly IGlobalOptionService GlobalOptions; + public readonly EditorOptionsService EditorOptionsService; protected bool _indentCaretOnCommit; protected int _indentDepth; @@ -39,13 +40,18 @@ internal abstract class AbstractSnippetExpansionClient : IExpansionClient public IExpansionSession? ExpansionSession { get; private set; } - public AbstractSnippetExpansionClient(IContentType languageServiceGuid, ITextView textView, ITextBuffer subjectBuffer, IExpansionServiceProvider expansionServiceProvider, IGlobalOptionService globalOptions) + public AbstractSnippetExpansionClient( + IContentType languageServiceGuid, + ITextView textView, + ITextBuffer subjectBuffer, + IExpansionServiceProvider expansionServiceProvider, + EditorOptionsService editorOptionsService) { LanguageServiceGuid = languageServiceGuid; TextView = textView; SubjectBuffer = subjectBuffer; ExpansionServiceProvider = expansionServiceProvider; - GlobalOptions = globalOptions; + EditorOptionsService = editorOptionsService; } public abstract IExpansionFunction? GetExpansionFunction(XElement xmlFunctionNode, string fieldName); @@ -93,7 +99,7 @@ public void FormatSpan(SnapshotSpan span) var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(SubjectBuffer.CurrentSnapshot, snippetTrackingSpan.GetSpan(SubjectBuffer.CurrentSnapshot)); - SubjectBuffer.CurrentSnapshot.FormatAndApplyToBuffer(formattingSpan, GlobalOptions, CancellationToken.None); + SubjectBuffer.FormatAndApplyToBuffer(formattingSpan, EditorOptionsService, CancellationToken.None); if (isFullSnippetFormat) { @@ -147,7 +153,7 @@ private void CleanUpEndLocation(ITrackingSpan? endTrackingSpan) var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document != null) { - var lineFormattingOptions = document.GetLineFormattingOptionsAsync(GlobalOptions, CancellationToken.None).AsTask().WaitAndGetResult(CancellationToken.None); + var lineFormattingOptions = SubjectBuffer.GetLineFormattingOptions(EditorOptionsService, explicitFormat: false); _indentDepth = lineText.GetColumnFromLineOffset(lineText.Length, lineFormattingOptions.TabSize); } else diff --git a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetCommandHandler.cs index eee35719dac32..5841500fb2d03 100644 --- a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetCommandHandler.cs @@ -41,8 +41,8 @@ public SnippetCommandHandler( IThreadingContext threadingContext, IExpansionServiceProvider expansionServiceProvider, IExpansionManager expansionManager, - IGlobalOptionService globalOptions) - : base(threadingContext, expansionServiceProvider, expansionManager, globalOptions) + EditorOptionsService editorOptionsService) + : base(threadingContext, expansionServiceProvider, expansionManager, editorOptionsService) { } @@ -84,7 +84,7 @@ protected override AbstractSnippetExpansionClient GetSnippetExpansionClient(ITex { if (!textView.Properties.TryGetProperty(typeof(AbstractSnippetExpansionClient), out AbstractSnippetExpansionClient expansionClient)) { - expansionClient = new SnippetExpansionClient(subjectBuffer.ContentType, textView, subjectBuffer, ExpansionServiceProvider, GlobalOptions); + expansionClient = new SnippetExpansionClient(subjectBuffer.ContentType, textView, subjectBuffer, ExpansionServiceProvider, EditorOptionsService); textView.Properties.AddProperty(typeof(AbstractSnippetExpansionClient), expansionClient); } diff --git a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetExpansionClient.cs b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetExpansionClient.cs index 6243391dd4f7c..267e1d65ae4b1 100644 --- a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetExpansionClient.cs +++ b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetExpansionClient.cs @@ -18,8 +18,13 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Snippets { internal sealed partial class SnippetExpansionClient : AbstractSnippetExpansionClient { - public SnippetExpansionClient(IContentType languageServiceGuid, ITextView textView, ITextBuffer subjectBuffer, IExpansionServiceProvider expansionServiceProvider, IGlobalOptionService globalOptions) - : base(languageServiceGuid, textView, subjectBuffer, expansionServiceProvider, globalOptions) + public SnippetExpansionClient( + IContentType languageServiceGuid, + ITextView textView, + ITextBuffer subjectBuffer, + IExpansionServiceProvider expansionServiceProvider, + EditorOptionsService editorOptionsService) + : base(languageServiceGuid, textView, subjectBuffer, expansionServiceProvider, editorOptionsService) { } diff --git a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs index 957ae572478e0..ac303228d7fb4 100644 --- a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs +++ b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs @@ -96,7 +96,7 @@ protected override bool TryGetSimplifiedTypeNameInCaseContext(Document document, var updatedRoot = syntaxRoot.ReplaceNode(nodeToReplace, nodeToReplace.WithAdditionalAnnotations(typeAnnotation, Simplifier.Annotation)); var documentWithAnnotations = documentWithCaseAdded.WithSyntaxRoot(updatedRoot); - var simplifierOptions = document.GetSimplifierOptionsAsync(_snippetExpansionClient.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var simplifierOptions = document.GetSimplifierOptionsAsync(_snippetExpansionClient.EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); var simplifiedDocument = Simplifier.ReduceAsync(documentWithAnnotations, simplifierOptions, cancellationToken).WaitAndGetResult(cancellationToken); simplifiedTypeName = simplifiedDocument.GetRequiredSyntaxRootSynchronously(cancellationToken).GetAnnotatedNodesAndTokens(typeAnnotation).Single().ToString(); return true; diff --git a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs index a9be002ddc488..9c415f000a93d 100644 --- a/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs +++ b/src/EditorFeatures/Core.Cocoa/Snippets/CSharpSnippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs @@ -37,7 +37,7 @@ protected override bool TryGetSimplifiedTypeName(Document documentWithFullyQuali var updatedRoot = syntaxRoot.ReplaceNode(nodeToReplace, nodeToReplace.WithAdditionalAnnotations(typeAnnotation, Simplifier.Annotation)); var documentWithAnnotations = documentWithFullyQualifiedTypeName.WithSyntaxRoot(updatedRoot); - var simplifierOptions = documentWithAnnotations.GetSimplifierOptionsAsync(_snippetExpansionClient.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var simplifierOptions = documentWithAnnotations.GetSimplifierOptionsAsync(_snippetExpansionClient.EditorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); var simplifiedDocument = Simplifier.ReduceAsync(documentWithAnnotations, simplifierOptions, cancellationToken).WaitAndGetResult(cancellationToken); simplifiedTypeName = simplifiedDocument.GetRequiredSyntaxRootSynchronously(cancellationToken).GetAnnotatedNodesAndTokens(typeAnnotation).Single().ToString(); return true; diff --git a/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs b/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs index e52963de22fee..a460ca3b5fa04 100644 --- a/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs +++ b/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs @@ -26,12 +26,11 @@ internal partial class StructureTaggerProvider : [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public StructureTaggerProvider( IThreadingContext threadingContext, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IProjectionBufferFactoryService projectionBufferFactoryService, - IGlobalOptionService globalOptions, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, editorOptionsFactoryService, projectionBufferFactoryService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, editorOptionsService, projectionBufferFactoryService, visibilityTracker, listenerProvider) { } diff --git a/src/EditorFeatures/Core.Wpf/AsyncCompletion/LanguageServerSnippetExpanderAdapter.cs b/src/EditorFeatures/Core.Wpf/AsyncCompletion/LanguageServerSnippetExpanderAdapter.cs new file mode 100644 index 0000000000000..b98a0d39d296c --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/AsyncCompletion/LanguageServerSnippetExpanderAdapter.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 System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServer.Client.Snippets; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion +{ + [Export(typeof(ILanguageServerSnippetExpander))] + [Shared] + internal sealed class LanguageServerSnippetExpanderAdapter : ILanguageServerSnippetExpander + { + private readonly LanguageServerSnippetExpander _languageServerSnippetExpander; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public LanguageServerSnippetExpanderAdapter(LanguageServerSnippetExpander languageServerSnippetExpander) + { + _languageServerSnippetExpander = languageServerSnippetExpander; + } + + public bool TryExpand(string lspSnippetText, SnapshotSpan snapshotSpan, ITextView textView) + => _languageServerSnippetExpander.TryExpand(lspSnippetText, snapshotSpan, textView); + } +} diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs b/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs index 8e357e7501559..cd77837c6eba1 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs @@ -11,9 +11,8 @@ using Microsoft.VisualStudio.Utilities; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Telemetry; -using System.Windows; +using Microsoft.CodeAnalysis.Shared.TestHooks; #if !COCOA using System.Linq; @@ -36,8 +35,11 @@ internal partial class RenameCommandHandler : AbstractRenameCommandHandler { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RenameCommandHandler(IThreadingContext threadingContext, InlineRenameService renameService) - : base(threadingContext, renameService) + public RenameCommandHandler( + IThreadingContext threadingContext, + InlineRenameService renameService, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider) + : base(threadingContext, renameService, asynchronousOperationListenerProvider) { } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveCommandHandler.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveCommandHandler.cs index 950f687e7af53..0d9ff77432de6 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveCommandHandler.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveCommandHandler.cs @@ -17,6 +17,7 @@ using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text.Editor.Commanding; +using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Interactive { @@ -25,16 +26,16 @@ internal abstract class InteractiveCommandHandler : ICommandHandler { private readonly IContentTypeRegistryService _contentTypeRegistryService; - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; protected InteractiveCommandHandler( IContentTypeRegistryService contentTypeRegistryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IEditorOperationsFactoryService editorOperationsFactoryService) { _contentTypeRegistryService = contentTypeRegistryService; - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; _editorOperationsFactoryService = editorOperationsFactoryService; } @@ -48,7 +49,7 @@ protected InteractiveCommandHandler( private string GetSelectedText(EditorCommandArgs args, CancellationToken cancellationToken) { - var editorOptions = _editorOptionsFactoryService.GetOptions(args.SubjectBuffer); + var editorOptions = _editorOptionsService.Factory.GetOptions(args.SubjectBuffer); return SendToInteractiveSubmissionProvider.GetSelectedText(editorOptions, args, cancellationToken); } @@ -113,7 +114,7 @@ private void CopyToWindow(IInteractiveWindow window, CopyToInteractiveCommandArg var lastLine = buffer.CurrentSnapshot.GetLineFromLineNumber(buffer.CurrentSnapshot.LineCount - 1); if (lastLine.Extent.Length > 0) { - var editorOptions = _editorOptionsFactoryService.GetOptions(args.SubjectBuffer); + var editorOptions = _editorOptionsService.Factory.GetOptions(args.SubjectBuffer); text = editorOptions.GetNewLineCharacter() + text; } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs index 7e767904a1a2b..3e86f9f8a8523 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs @@ -26,6 +26,7 @@ namespace Microsoft.CodeAnalysis.Interactive { using InteractiveHost::Microsoft.CodeAnalysis.Interactive; + using Microsoft.VisualStudio.Text.Editor; // TODO: Rename to InteractiveEvaluator https://github.com/dotnet/roslyn/issues/6441 // The code is not specific to C#, but Interactive Window has hardcoded "CSharpInteractiveEvaluator" name. @@ -62,7 +63,6 @@ internal sealed class CSharpInteractiveEvaluator : IResettableInteractiveEvaluat = new InteractiveEvaluatorResetOptions(InteractiveHostPlatform.Desktop64); internal CSharpInteractiveEvaluator( - IGlobalOptionService globalOptions, IThreadingContext threadingContext, IAsynchronousOperationListener listener, IContentType contentType, @@ -71,6 +71,7 @@ internal CSharpInteractiveEvaluator( IInteractiveWindowCommandsFactory commandsFactory, ImmutableArray commands, ITextDocumentFactoryService textDocumentFactoryService, + EditorOptionsService editorOptionsService, InteractiveEvaluatorLanguageInfoProvider languageInfo, string initialWorkingDirectory) { @@ -83,9 +84,17 @@ internal CSharpInteractiveEvaluator( _commandsFactory = commandsFactory; _commands = commands; - _workspace = new InteractiveWindowWorkspace(hostServices, globalOptions); + _workspace = new InteractiveWindowWorkspace(hostServices, editorOptionsService.GlobalOptions); + + _session = new InteractiveSession( + _workspace, + threadingContext, + listener, + textDocumentFactoryService, + editorOptionsService, + languageInfo, + initialWorkingDirectory); - _session = new InteractiveSession(_workspace, threadingContext, listener, textDocumentFactoryService, languageInfo, initialWorkingDirectory); _session.Host.ProcessInitialized += ProcessInitialized; } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs b/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs index d298561b31a73..d674c005a943d 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; @@ -30,13 +31,13 @@ internal abstract class ResetInteractive private readonly Func _createImport; - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; internal event EventHandler ExecutionCompleted; - internal ResetInteractive(IEditorOptionsFactoryService editorOptionsFactoryService, Func createReference, Func createImport) + internal ResetInteractive(EditorOptionsService editorOptionsService, Func createReference, Func createImport) { - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; _createReference = createReference; _createImport = createImport; } @@ -110,7 +111,7 @@ private async Task ResetInteractiveAsync( // Now send the reference paths we've collected to the repl. await evaluator.SetPathsAsync(referenceSearchPaths, sourceSearchPaths, projectDirectory).ConfigureAwait(true); - var editorOptions = _editorOptionsFactoryService.GetOptions(interactiveWindow.CurrentLanguageBuffer); + var editorOptions = _editorOptionsService.Factory.GetOptions(interactiveWindow.CurrentLanguageBuffer); var importReferencesCommand = referencePaths.Select(_createReference); await interactiveWindow.SubmitAsync(importReferencesCommand).ConfigureAwait(true); diff --git a/src/EditorFeatures/Core.Wpf/Notification/EditorNotificationServiceFactory.cs b/src/EditorFeatures/Core.Wpf/Notification/EditorNotificationServiceFactory.cs index b18d957c63a33..41071d58ae1fa 100644 --- a/src/EditorFeatures/Core.Wpf/Notification/EditorNotificationServiceFactory.cs +++ b/src/EditorFeatures/Core.Wpf/Notification/EditorNotificationServiceFactory.cs @@ -31,10 +31,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { lock (s_gate) { - if (s_singleton == null) - { - s_singleton = new EditorDialogService(); - } + s_singleton ??= new EditorDialogService(); } return s_singleton; diff --git a/src/EditorFeatures/Core.Wpf/Preview/PreviewFactoryService.cs b/src/EditorFeatures/Core.Wpf/Preview/PreviewFactoryService.cs index 65940f2ea8c80..79e8bd3346973 100644 --- a/src/EditorFeatures/Core.Wpf/Preview/PreviewFactoryService.cs +++ b/src/EditorFeatures/Core.Wpf/Preview/PreviewFactoryService.cs @@ -33,21 +33,19 @@ public PreviewFactoryService( IContentTypeRegistryService contentTypeRegistryService, IProjectionBufferFactoryService projectionBufferFactoryService, ITextEditorFactoryService textEditorFactoryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, ITextDifferencingSelectorService differenceSelectorService, IDifferenceBufferFactoryService differenceBufferService, - IWpfDifferenceViewerFactoryService differenceViewerService, - IGlobalOptionService globalOptions) + IWpfDifferenceViewerFactoryService differenceViewerService) : base(threadingContext, textBufferFactoryService, contentTypeRegistryService, projectionBufferFactoryService, - editorOptionsFactoryService, + editorOptionsService, differenceSelectorService, differenceBufferService, textEditorFactoryService.CreateTextViewRoleSet( - TextViewRoles.PreviewRole, PredefinedTextViewRoles.Analyzable), - globalOptions) + TextViewRoles.PreviewRole, PredefinedTextViewRoles.Analyzable)) { _differenceViewerService = differenceViewerService; } diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ContentControlService.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ContentControlService.cs index f72d641be89c9..ec89e90feaa12 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/ContentControlService.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ContentControlService.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Preview; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; @@ -30,7 +31,7 @@ internal partial class ContentControlService : IContentControlService private readonly ITextEditorFactoryService _textEditorFactoryService; private readonly IContentTypeRegistryService _contentTypeRegistryService; private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -39,13 +40,13 @@ public ContentControlService( ITextEditorFactoryService textEditorFactoryService, IContentTypeRegistryService contentTypeRegistryService, IProjectionBufferFactoryService projectionBufferFactoryService, - IEditorOptionsFactoryService editorOptionsFactoryService) + EditorOptionsService editorOptionsService) { _threadingContext = threadingContext; _textEditorFactoryService = textEditorFactoryService; _contentTypeRegistryService = contentTypeRegistryService; _projectionBufferFactoryService = projectionBufferFactoryService; - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; } public void AttachToolTipToControl(FrameworkElement element, Func createToolTip) @@ -105,7 +106,7 @@ public ViewHostingControl CreateViewHostingControl(ITextBuffer textBuffer, Span _threadingContext, ImmutableArray.Create(snapshotSpan), _projectionBufferFactoryService, - _editorOptionsFactoryService, + _editorOptionsService, _textEditorFactoryService, contentType, roleSet); diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs index 2d55131c0d249..a0bed1d52547c 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs @@ -9,6 +9,7 @@ using System.Windows.Media; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Projection; @@ -25,7 +26,7 @@ internal class ProjectionBufferContent : ForegroundThreadAffinitizedObject { private readonly ImmutableArray _spans; private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; private readonly ITextEditorFactoryService _textEditorFactoryService; private readonly IContentType _contentType; private readonly ITextViewRoleSet _roleSet; @@ -34,7 +35,7 @@ private ProjectionBufferContent( IThreadingContext threadingContext, ImmutableArray spans, IProjectionBufferFactoryService projectionBufferFactoryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, ITextEditorFactoryService textEditorFactoryService, IContentType contentType = null, ITextViewRoleSet roleSet = null) @@ -42,7 +43,7 @@ private ProjectionBufferContent( { _spans = spans; _projectionBufferFactoryService = projectionBufferFactoryService; - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; _textEditorFactoryService = textEditorFactoryService; _contentType = contentType; _roleSet = roleSet ?? _textEditorFactoryService.NoRoles; @@ -52,7 +53,7 @@ public static ViewHostingControl Create( IThreadingContext threadingContext, ImmutableArray spans, IProjectionBufferFactoryService projectionBufferFactoryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, ITextEditorFactoryService textEditorFactoryService, IContentType contentType = null, ITextViewRoleSet roleSet = null) @@ -61,7 +62,7 @@ public static ViewHostingControl Create( threadingContext, spans, projectionBufferFactoryService, - editorOptionsFactoryService, + editorOptionsService, textEditorFactoryService, contentType, roleSet); @@ -95,7 +96,7 @@ private IWpfTextView CreateView(ITextBuffer buffer) private IProjectionBuffer CreateBuffer() { return _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( - _editorOptionsFactoryService.GlobalOptions, _contentType, _spans.ToArray()); + _editorOptionsService.Factory.GlobalOptions, _contentType, _spans.ToArray()); } } } diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs index 3d9b05ad89458..26def9f12de74 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs @@ -163,11 +163,8 @@ private static SignatureHelpItem GetSelectedItem(Model currentModel, SignatureHe lastSelectionOrDefault = items.Items.FirstOrDefault(i => DisplayPartsMatch(i, currentModel.SelectedItem)); } - if (lastSelectionOrDefault == null) - { - // Otherwise, just pick the first item we have. - lastSelectionOrDefault = items.Items.First(); - } + // Otherwise, just pick the first item we have. + lastSelectionOrDefault ??= items.Items.First(); return lastSelectionOrDefault; } diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/Parameter.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/Parameter.cs index 7bb556d899e3e..fc5b91ce806b3 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/Parameter.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/Parameter.cs @@ -20,7 +20,7 @@ internal class Parameter : IParameter private readonly int _index; private readonly int _prettyPrintedIndex; - public string Documentation => _documentation ?? (_documentation = _parameter.DocumentationFactory(CancellationToken.None).GetFullText()); + public string Documentation => _documentation ??= _parameter.DocumentationFactory(CancellationToken.None).GetFullText(); public string Name => _parameter.Name; public Span Locus => new(_index, _contentLength); public Span PrettyPrintedLocus => new(_prettyPrintedIndex, _contentLength); diff --git a/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs index 5c629e8caa685..8d1cee5339a30 100644 --- a/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs @@ -34,13 +34,13 @@ internal class StructureTaggerProvider : [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public StructureTaggerProvider( IThreadingContext threadingContext, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IProjectionBufferFactoryService projectionBufferFactoryService, ITextEditorFactoryService textEditorFactoryService, IGlobalOptionService globalOptions, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, editorOptionsFactoryService, projectionBufferFactoryService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, editorOptionsService, projectionBufferFactoryService, visibilityTracker, listenerProvider) { _textEditorFactoryService = textEditorFactoryService; } diff --git a/src/EditorFeatures/Core.Wpf/ViewHostingControl.cs b/src/EditorFeatures/Core.Wpf/ViewHostingControl.cs index d56161ad6a8c6..7a5ab9acef507 100644 --- a/src/EditorFeatures/Core.Wpf/ViewHostingControl.cs +++ b/src/EditorFeatures/Core.Wpf/ViewHostingControl.cs @@ -35,10 +35,7 @@ public ViewHostingControl( private void EnsureBufferCreated() { - if (_createdTextBuffer == null) - { - _createdTextBuffer = _createBuffer(); - } + _createdTextBuffer ??= _createBuffer(); } private void EnsureContentCreated() diff --git a/src/EditorFeatures/Core/AutomaticCompletion/AbstractAutomaticLineEnderCommandHandler.cs b/src/EditorFeatures/Core/AutomaticCompletion/AbstractAutomaticLineEnderCommandHandler.cs index a7b717b0dc534..a923c10ce9a46 100644 --- a/src/EditorFeatures/Core/AutomaticCompletion/AbstractAutomaticLineEnderCommandHandler.cs +++ b/src/EditorFeatures/Core/AutomaticCompletion/AbstractAutomaticLineEnderCommandHandler.cs @@ -25,18 +25,19 @@ internal abstract class AbstractAutomaticLineEnderCommandHandler : { private readonly ITextUndoHistoryRegistry _undoRegistry; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; - public readonly IGlobalOptionService GlobalOptions; + + public readonly EditorOptionsService EditorOptionsService; public string DisplayName => EditorFeaturesResources.Automatic_Line_Ender; protected AbstractAutomaticLineEnderCommandHandler( ITextUndoHistoryRegistry undoRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) + EditorOptionsService editorOptionsService) { _undoRegistry = undoRegistry; _editorOperationsFactoryService = editorOperationsFactoryService; - GlobalOptions = globalOptions; + EditorOptionsService = editorOptionsService; } /// @@ -90,7 +91,7 @@ public void ExecuteCommand(AutomaticLineEnderCommandArgs args, Action nextHandle } // feature off - if (!GlobalOptions.GetOption(InternalFeatureOnOffOptions.AutomaticLineEnder)) + if (!EditorOptionsService.GlobalOptions.GetOption(InternalFeatureOnOffOptions.AutomaticLineEnder)) { NextAction(operations, nextHandler); return; @@ -142,7 +143,7 @@ public void ExecuteCommand(AutomaticLineEnderCommandArgs args, Action nextHandle if (endingInsertionPosition != null) { using var transaction = args.TextView.CreateEditTransaction(EditorFeaturesResources.Automatic_Line_Ender, _undoRegistry, _editorOperationsFactoryService); - var formattingOptions = document.GetSyntaxFormattingOptionsAsync(GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var formattingOptions = args.SubjectBuffer.GetSyntaxFormattingOptions(EditorOptionsService, document.Project.LanguageServices, explicitFormat: false); InsertEnding(args.TextView, document, endingInsertionPosition.Value, caretPosition, formattingOptions, cancellationToken); NextAction(operations, nextHandler); transaction.Complete(); diff --git a/src/EditorFeatures/Core/AutomaticCompletion/AbstractBraceCompletionServiceFactory.cs b/src/EditorFeatures/Core/AutomaticCompletion/AbstractBraceCompletionServiceFactory.cs index 0f3de0920809c..e3b9376318f60 100644 --- a/src/EditorFeatures/Core/AutomaticCompletion/AbstractBraceCompletionServiceFactory.cs +++ b/src/EditorFeatures/Core/AutomaticCompletion/AbstractBraceCompletionServiceFactory.cs @@ -20,11 +20,11 @@ protected AbstractBraceCompletionServiceFactory( _braceCompletionServices = braceCompletionServices.ToImmutableArray(); } - public async Task TryGetServiceAsync(Document document, int openingPosition, char openingBrace, CancellationToken cancellationToken) + public IBraceCompletionService? TryGetService(ParsedDocument document, int openingPosition, char openingBrace, CancellationToken cancellationToken) { foreach (var service in _braceCompletionServices) { - if (await service.CanProvideBraceCompletionAsync(openingBrace, openingPosition, document, cancellationToken).ConfigureAwait(false)) + if (service.CanProvideBraceCompletion(openingBrace, openingPosition, document, cancellationToken)) { return service; } diff --git a/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.BraceCompletionSession.cs b/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.BraceCompletionSession.cs index 86d5652e9896a..7cd8982080d0e 100644 --- a/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.BraceCompletionSession.cs +++ b/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.BraceCompletionSession.cs @@ -44,15 +44,15 @@ private class BraceCompletionSession : IBraceCompletionSession private readonly ITextUndoHistory _undoHistory; private readonly IEditorOperations _editorOperations; + private readonly EditorOptionsService _editorOptionsService; private readonly IBraceCompletionService _service; - private readonly IGlobalOptionService _globalOptions; private readonly IThreadingContext _threadingContext; public BraceCompletionSession( ITextView textView, ITextBuffer subjectBuffer, SnapshotPoint openingPoint, char openingBrace, char closingBrace, ITextUndoHistory undoHistory, - IEditorOperationsFactoryService editorOperationsFactoryService, IBraceCompletionService service, - IGlobalOptionService globalOptions, IThreadingContext threadingContext) + IEditorOperationsFactoryService editorOperationsFactoryService, + EditorOptionsService editorOptionsService, IBraceCompletionService service, IThreadingContext threadingContext) { TextView = textView; SubjectBuffer = subjectBuffer; @@ -61,9 +61,9 @@ public BraceCompletionSession( ClosingPoint = SubjectBuffer.CurrentSnapshot.CreateTrackingPoint(openingPoint.Position, PointTrackingMode.Positive); _undoHistory = undoHistory; _editorOperations = editorOperationsFactoryService.GetEditorOperations(textView); + _editorOptionsService = editorOptionsService; _service = service; _threadingContext = threadingContext; - _globalOptions = globalOptions; } #region IBraceCompletionSession Methods @@ -101,33 +101,35 @@ private bool TryStart(CancellationToken cancellationToken) OpeningPoint = SubjectBuffer.CurrentSnapshot.CreateTrackingPoint(openingSnapshotPoint, PointTrackingMode.Positive); - var context = GetBraceCompletionContext(); - if (context == null) + var document = SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) { return false; } - var braceResult = _service.GetBraceCompletionAsync(context.Value, cancellationToken).WaitAndGetResult(cancellationToken); - if (braceResult == null) + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var context = GetBraceCompletionContext(parsedDocument); + + // Note: completes synchronously unless Semantic Model is needed to determine the result: + if (!_service.HasBraceCompletionAsync(context, document, cancellationToken).WaitAndGetResult(cancellationToken)) { return false; } + var braceResult = _service.GetBraceCompletion(context); + using var caretPreservingTransaction = new CaretPreservingEditTransaction(EditorFeaturesResources.Brace_Completion, _undoHistory, _editorOperations); // Apply the change to complete the brace. - ApplyBraceCompletionResult(braceResult.Value); + ApplyBraceCompletionResult(braceResult); // switch the closing point from positive to negative tracking so that the closing point stays against the closing brace ClosingPoint = SubjectBuffer.CurrentSnapshot.CreateTrackingPoint(ClosingPoint.GetPoint(SubjectBuffer.CurrentSnapshot), PointTrackingMode.Negative); - var contextAfterStart = GetBraceCompletionContext(); - if (contextAfterStart != null) + if (TryGetBraceCompletionContext(out var contextAfterStart, cancellationToken)) { - var document = contextAfterStart.Value.Document; - var indentationOptions = document.GetIndentationOptionsAsync(_globalOptions, cancellationToken).WaitAndGetResult(cancellationToken); - - var changesAfterStart = _service.GetTextChangesAfterCompletionAsync(contextAfterStart.Value, indentationOptions, cancellationToken).WaitAndGetResult(cancellationToken); + var indentationOptions = SubjectBuffer.GetIndentationOptions(_editorOptionsService, contextAfterStart.Document.LanguageServices, explicitFormat: false); + var changesAfterStart = _service.GetTextChangesAfterCompletion(contextAfterStart, indentationOptions, cancellationToken); if (changesAfterStart != null) { ApplyBraceCompletionResult(changesAfterStart.Value); @@ -193,52 +195,52 @@ public void PreOverType(out bool handledCommand) var closingSnapshotPoint = ClosingPoint.GetPoint(snapshot); - if (!HasForwardTyping && AllowOverType()) + if (HasForwardTyping) { - var caretPos = this.GetCaretPosition(); + return; + } - Debug.Assert(caretPos.HasValue && caretPos.Value.Position < closingSnapshotPoint.Position); + if (!TryGetBraceCompletionContext(out var context, cancellationToken) || + !_service.AllowOverType(context, cancellationToken)) + { + return; + } - // ensure that we are within the session before clearing - if (caretPos.HasValue && caretPos.Value.Position < closingSnapshotPoint.Position && closingSnapshotPoint.Position > 0) - { - using var undo = CreateUndoTransaction(); + var caretPos = this.GetCaretPosition(); - _editorOperations.AddBeforeTextBufferChangePrimitive(); + Debug.Assert(caretPos.HasValue && caretPos.Value.Position < closingSnapshotPoint.Position); - var span = new SnapshotSpan(caretPos.Value, closingSnapshotPoint.Subtract(1)); + // ensure that we are within the session before clearing + if (caretPos.HasValue && caretPos.Value.Position < closingSnapshotPoint.Position && closingSnapshotPoint.Position > 0) + { + using var undo = CreateUndoTransaction(); - using var edit = SubjectBuffer.CreateEdit(); + _editorOperations.AddBeforeTextBufferChangePrimitive(); - edit.Delete(span); + var span = new SnapshotSpan(caretPos.Value, closingSnapshotPoint.Subtract(1)); - if (edit.HasFailedChanges) - { - Debug.Fail("Unable to clear closing brace"); - edit.Cancel(); - undo.Cancel(); - } - else - { - handledCommand = true; + using var edit = SubjectBuffer.CreateEdit(); - edit.ApplyAndLogExceptions(); + edit.Delete(span); - MoveCaretToClosingPoint(); + if (edit.HasFailedChanges) + { + Debug.Fail("Unable to clear closing brace"); + edit.Cancel(); + undo.Cancel(); + } + else + { + handledCommand = true; - _editorOperations.AddAfterTextBufferChangePrimitive(); + edit.ApplyAndLogExceptions(); - undo.Complete(); - } - } - } + MoveCaretToClosingPoint(); - return; + _editorOperations.AddAfterTextBufferChangePrimitive(); - bool AllowOverType() - { - var context = GetBraceCompletionContext(); - return context != null && _service.AllowOverTypeAsync(context.Value, cancellationToken).WaitAndGetResult(cancellationToken); + undo.Complete(); + } } } @@ -279,14 +281,13 @@ public void PostReturn() if (closingSnapshotPoint.Position > 0 && HasNoForwardTyping(this.GetCaretPosition().Value, closingSnapshotPoint.Subtract(1))) { - var context = GetBraceCompletionContext(); - if (context == null) + if (!TryGetBraceCompletionContext(out var context, CancellationToken.None)) { return; } - var indentationOptions = context.Value.Document.GetIndentationOptionsAsync(_globalOptions, CancellationToken.None).WaitAndGetResult(CancellationToken.None); - var changesAfterReturn = _service.GetTextChangeAfterReturnAsync(context.Value, indentationOptions, CancellationToken.None).WaitAndGetResult(CancellationToken.None); + var indentationOptions = SubjectBuffer.GetIndentationOptions(_editorOptionsService, context.Document.LanguageServices, explicitFormat: false); + var changesAfterReturn = _service.GetTextChangeAfterReturn(context, indentationOptions, CancellationToken.None); if (changesAfterReturn != null) { using var caretPreservingTransaction = new CaretPreservingEditTransaction(EditorFeaturesResources.Brace_Completion, _undoHistory, _editorOperations); @@ -390,19 +391,29 @@ private void MoveCaretToClosingPoint() } } - private BraceCompletionContext? GetBraceCompletionContext() + private bool TryGetBraceCompletionContext(out BraceCompletionContext context, CancellationToken cancellationToken) + { + var document = SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + { + context = default; + return false; + } + + context = GetBraceCompletionContext(ParsedDocument.CreateSynchronously(document, cancellationToken)); + return true; + } + + private BraceCompletionContext GetBraceCompletionContext(ParsedDocument document) { _threadingContext.ThrowIfNotOnUIThread(); var snapshot = SubjectBuffer.CurrentSnapshot; - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - return null; - var closingSnapshotPoint = ClosingPoint.GetPosition(snapshot); var openingSnapshotPoint = OpeningPoint.GetPosition(snapshot); // The user is actively typing so the caret position should not be null. var caretPosition = this.GetCaretPosition().Value.Position; + return new BraceCompletionContext(document, openingSnapshotPoint, closingSnapshotPoint, caretPosition); } diff --git a/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.cs b/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.cs index d1abdc5e9b424..7f9508b4bb1f5 100644 --- a/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.cs +++ b/src/EditorFeatures/Core/AutomaticCompletion/BraceCompletionSessionProvider.cs @@ -37,7 +37,7 @@ internal partial class BraceCompletionSessionProvider : IBraceCompletionSessionP private readonly IThreadingContext _threadingContext; private readonly ITextBufferUndoManagerProvider _undoManager; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; - private readonly IGlobalOptionService _globalOptions; + private readonly EditorOptionsService _editorOptionsService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -45,12 +45,12 @@ public BraceCompletionSessionProvider( IThreadingContext threadingContext, ITextBufferUndoManagerProvider undoManager, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) + EditorOptionsService editorOptionsService) { _threadingContext = threadingContext; _undoManager = undoManager; _editorOperationsFactoryService = editorOperationsFactoryService; - _globalOptions = globalOptions; + _editorOptionsService = editorOptionsService; } public bool TryCreateSession(ITextView textView, SnapshotPoint openingPoint, char openingBrace, char closingBrace, out IBraceCompletionSession session) @@ -66,14 +66,15 @@ public bool TryCreateSession(ITextView textView, SnapshotPoint openingPoint, cha // Brace completion is (currently) not cancellable. var cancellationToken = CancellationToken.None; - var editorSession = editorSessionFactory.TryGetServiceAsync(document, openingPoint, openingBrace, cancellationToken).WaitAndGetResult(cancellationToken); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var editorSession = editorSessionFactory.TryGetService(parsedDocument, openingPoint, openingBrace, cancellationToken); if (editorSession != null) { var undoHistory = _undoManager.GetTextBufferUndoManager(textView.TextBuffer).TextBufferUndoHistory; session = new BraceCompletionSession( textView, openingPoint.Snapshot.TextBuffer, openingPoint, openingBrace, closingBrace, - undoHistory, _editorOperationsFactoryService, - editorSession, _globalOptions, _threadingContext); + undoHistory, _editorOperationsFactoryService, _editorOptionsService, + editorSession, _threadingContext); return true; } } diff --git a/src/EditorFeatures/Core/AutomaticCompletion/IBraceCompletionServiceFactory.cs b/src/EditorFeatures/Core/AutomaticCompletion/IBraceCompletionServiceFactory.cs index 4c5c03440d6c5..35ffe020ab447 100644 --- a/src/EditorFeatures/Core/AutomaticCompletion/IBraceCompletionServiceFactory.cs +++ b/src/EditorFeatures/Core/AutomaticCompletion/IBraceCompletionServiceFactory.cs @@ -11,6 +11,6 @@ namespace Microsoft.CodeAnalysis.AutomaticCompletion { internal interface IBraceCompletionServiceFactory : ILanguageService { - Task TryGetServiceAsync(Document document, int openingPosition, char openingBrace, CancellationToken cancellationToken); + IBraceCompletionService? TryGetService(ParsedDocument document, int openingPosition, char openingBrace, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/ChangeSignature/AbstractChangeSignatureCommandHandler.cs b/src/EditorFeatures/Core/ChangeSignature/AbstractChangeSignatureCommandHandler.cs index c4d13cb114611..407a22c414494 100644 --- a/src/EditorFeatures/Core/ChangeSignature/AbstractChangeSignatureCommandHandler.cs +++ b/src/EditorFeatures/Core/ChangeSignature/AbstractChangeSignatureCommandHandler.cs @@ -80,6 +80,9 @@ private bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, Comma var cancellationToken = context.OperationContext.UserCancellationToken; + // TODO: Make asynchronous and avoid expensive semantic operations on UI thread: + // https://github.com/dotnet/roslyn/issues/62135 + // Async operation to determine the change signature var changeSignatureContext = changeSignatureService.GetChangeSignatureContextAsync( document, diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs index 2bbcc7e933097..67c86811f3174 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -45,19 +46,19 @@ internal abstract class AbstractCommentSelectionBase private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; - private readonly IGlobalOptionService _globalOptions; + private readonly EditorOptionsService _editorOptionsService; internal AbstractCommentSelectionBase( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) + EditorOptionsService editorOptionsService) { Contract.ThrowIfNull(undoHistoryRegistry); Contract.ThrowIfNull(editorOperationsFactoryService); _undoHistoryRegistry = undoHistoryRegistry; _editorOperationsFactoryService = editorOperationsFactoryService; - _globalOptions = globalOptions; + _editorOptionsService = editorOptionsService; } public abstract string DisplayName { get; } @@ -67,7 +68,7 @@ internal AbstractCommentSelectionBase( protected abstract string GetMessage(TCommand command); // Internal as tests currently rely on this method. - internal abstract Task CollectEditsAsync( + internal abstract CommentSelectionResult CollectEdits( Document document, ICommentSelectionService service, ITextBuffer textBuffer, NormalizedSnapshotSpanCollection selectedSpans, TCommand command, CancellationToken cancellationToken); @@ -111,9 +112,8 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, TCom return true; } - var edits = CollectEditsAsync(document, service, subjectBuffer, selectedSpans, command, cancellationToken).WaitAndGetResult(cancellationToken); - - ApplyEdits(document, textView, subjectBuffer, service, title, edits); + var edits = CollectEdits(document, service, subjectBuffer, selectedSpans, command, cancellationToken); + ApplyEdits(document, textView, subjectBuffer, title, edits, cancellationToken); } return true; @@ -123,18 +123,28 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, TCom /// Applies the requested edits and sets the selection. /// This operation is not cancellable. /// - private void ApplyEdits(Document document, ITextView textView, ITextBuffer subjectBuffer, - ICommentSelectionService service, string title, CommentSelectionResult edits) + private void ApplyEdits(Document document, ITextView textView, ITextBuffer subjectBuffer, string title, CommentSelectionResult edits, CancellationToken cancellationToken) { + var workspace = document.Project.Solution.Workspace; + // Create tracking spans to track the text changes. var currentSnapshot = subjectBuffer.CurrentSnapshot; var trackingSpans = edits.TrackingSpans .SelectAsArray(textSpan => (originalSpan: textSpan, trackingSpan: CreateTrackingSpan(edits.ResultOperation, currentSnapshot, textSpan.TrackingTextSpan))); // Apply the text changes. + SourceText newText; using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService)) { - document.Project.Solution.Workspace.ApplyTextChanges(document.Id, edits.TextChanges.Distinct(), CancellationToken.None); + var oldSolution = workspace.CurrentSolution; + + var oldDocument = oldSolution.GetRequiredDocument(document.Id); + var oldText = oldDocument.GetTextSynchronously(cancellationToken); + newText = oldText.WithChanges(edits.TextChanges.Distinct()); + + var newSolution = oldSolution.WithDocumentText(document.Id, newText, PreservationMode.PreserveIdentity); + workspace.TryApplyChanges(newSolution); + transaction.Complete(); } @@ -143,17 +153,22 @@ private void ApplyEdits(Document document, ITextView textView, ITextBuffer subje if (trackingSnapshotSpans.Any()) { - if (edits.ResultOperation == Operation.Uncomment) + if (edits.ResultOperation == Operation.Uncomment && document.SupportsSyntaxTree) { // Format the document only during uncomment operations. Use second transaction so it can be undone. using var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService); - var formattedDocument = Format(service, subjectBuffer.CurrentSnapshot, trackingSnapshotSpans, CancellationToken.None); - if (formattedDocument != null) - { - formattedDocument.Project.Solution.Workspace.ApplyDocumentChanges(formattedDocument, CancellationToken.None); - transaction.Complete(); - } + var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false); + + var updatedDocument = workspace.CurrentSolution.GetRequiredDocument(document.Id); + var root = updatedDocument.GetRequiredSyntaxRootSynchronously(cancellationToken); + + var formattingSpans = trackingSnapshotSpans.Select(change => CommonFormattingHelpers.GetFormattingSpan(root, change.Span.ToTextSpan())); + var formattedRoot = Formatter.Format(root, formattingSpans, workspace.Services, formattingOptions, rules: null, cancellationToken); + var formattedDocument = document.WithSyntaxRoot(formattedRoot); + + workspace.ApplyDocumentChanges(formattedDocument, cancellationToken); + transaction.Complete(); } // Set the multi selection after edits have been applied. @@ -194,19 +209,6 @@ private static SnapshotSpan CreateSnapshotSpan(ITextSnapshot snapshot, ITracking return snapshotSpan; } - private Document Format(ICommentSelectionService service, ITextSnapshot snapshot, IEnumerable changes, CancellationToken cancellationToken) - { - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - { - return null; - } - - var formattingOptions = document.GetSyntaxFormattingOptionsAsync(_globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); - var textSpans = changes.SelectAsArray(change => change.Span.ToTextSpan()); - return service.FormatAsync(document, textSpans, formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken); - } - /// /// Given a set of lines, find the minimum indent of all of the non-blank, non-whitespace lines. /// diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs index 69a30d2d3954c..e88a5991b5afc 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Operations; using Roslyn.Utilities; @@ -39,8 +40,8 @@ internal AbstractToggleBlockCommentBase( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, ITextStructureNavigatorSelectorService navigatorSelectorService, - IGlobalOptionService globalOptions) - : base(undoHistoryRegistry, editorOperationsFactoryService, globalOptions) + EditorOptionsService editorOptionsService) + : base(undoHistoryRegistry, editorOperationsFactoryService, editorOptionsService) { _navigatorSelectorService = navigatorSelectorService; } @@ -55,9 +56,8 @@ internal AbstractToggleBlockCommentBase( /// until the last character of the last line in the selection(s) /// /// the comment information for the document. - /// a cancellation token. /// any commented spans relevant to the selection in the document. - protected abstract Task> GetBlockCommentsInDocumentAsync(Document document, ITextSnapshot snapshot, + protected abstract ImmutableArray GetBlockCommentsInDocument(Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken); public CommandState GetCommandState(ToggleBlockCommentCommandArgs args) @@ -72,7 +72,7 @@ public bool ExecuteCommand(ToggleBlockCommentCommandArgs args, CommandExecutionC protected override string GetMessage(ValueTuple command) => EditorFeaturesResources.Toggling_block_comment; - internal override async Task CollectEditsAsync(Document document, ICommentSelectionService service, + internal override CommentSelectionResult CollectEdits(Document document, ICommentSelectionService service, ITextBuffer subjectBuffer, NormalizedSnapshotSpanCollection selectedSpans, ValueTuple command, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.CommandHandler_ToggleBlockComment, KeyValueLogMessage.Create(LogType.UserAction, m => @@ -83,24 +83,24 @@ internal override async Task CollectEditsAsync(Document { var navigator = _navigatorSelectorService.GetTextStructureNavigator(subjectBuffer); - var commentInfo = await service.GetInfoAsync(document, selectedSpans.First().Span.ToTextSpan(), cancellationToken).ConfigureAwait(false); + var commentInfo = service.GetInfo(); if (commentInfo.SupportsBlockComment) { - return await ToggleBlockCommentsAsync(document, commentInfo, navigator, selectedSpans, cancellationToken).ConfigureAwait(false); + return ToggleBlockComments(document, commentInfo, navigator, selectedSpans, cancellationToken); } return s_emptyCommentSelectionResult; } } - private async Task ToggleBlockCommentsAsync(Document document, CommentSelectionInfo commentInfo, + private CommentSelectionResult ToggleBlockComments(Document document, CommentSelectionInfo commentInfo, ITextStructureNavigator navigator, NormalizedSnapshotSpanCollection selectedSpans, CancellationToken cancellationToken) { var firstLineAroundSelection = selectedSpans.First().Start.GetContainingLine().Start; var lastLineAroundSelection = selectedSpans.Last().End.GetContainingLine().End; var linesContainingSelection = TextSpan.FromBounds(firstLineAroundSelection, lastLineAroundSelection); - var blockCommentedSpans = await GetBlockCommentsInDocumentAsync( - document, selectedSpans.First().Snapshot, linesContainingSelection, commentInfo, cancellationToken).ConfigureAwait(false); + var blockCommentedSpans = GetBlockCommentsInDocument( + document, selectedSpans.First().Snapshot, linesContainingSelection, commentInfo, cancellationToken); var blockCommentSelections = selectedSpans.SelectAsArray(span => new BlockCommentSelectionHelper(blockCommentedSpans, span)); diff --git a/src/EditorFeatures/Core/CommentSelection/CommentUncommentSelectionCommandHandler.cs b/src/EditorFeatures/Core/CommentSelection/CommentUncommentSelectionCommandHandler.cs index f032bd2ad35c8..8f037f025f73a 100644 --- a/src/EditorFeatures/Core/CommentSelection/CommentUncommentSelectionCommandHandler.cs +++ b/src/EditorFeatures/Core/CommentSelection/CommentUncommentSelectionCommandHandler.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Operations; using Roslyn.Utilities; @@ -38,8 +39,8 @@ internal class CommentUncommentSelectionCommandHandler : public CommentUncommentSelectionCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) - : base(undoHistoryRegistry, editorOperationsFactoryService, globalOptions) + EditorOptionsService editorOptionsService) + : base(undoHistoryRegistry, editorOperationsFactoryService, editorOptionsService) { } @@ -78,7 +79,7 @@ protected override string GetMessage(Operation operation) => /// /// Internal so that it can be called by unit tests. /// - internal override Task CollectEditsAsync( + internal override CommentSelectionResult CollectEdits( Document document, ICommentSelectionService service, ITextBuffer subjectBuffer, NormalizedSnapshotSpanCollection selectedSpans, Operation operation, CancellationToken cancellationToken) { @@ -88,23 +89,23 @@ internal override Task CollectEditsAsync( { if (operation == Operation.Comment) { - CommentSpan(document, service, span, textChanges, spanTrackingList, cancellationToken); + CommentSpan(service, span, textChanges, spanTrackingList); } else { - UncommentSpan(document, service, span, textChanges, spanTrackingList, cancellationToken); + UncommentSpan(service, span, textChanges, spanTrackingList); } } - return Task.FromResult(new CommentSelectionResult(textChanges.ToArrayAndFree(), spanTrackingList.ToArrayAndFree(), operation)); + return new CommentSelectionResult(textChanges.ToArrayAndFree(), spanTrackingList.ToArrayAndFree(), operation); } /// /// Add the necessary edits to comment out a single span. /// private static void CommentSpan( - Document document, ICommentSelectionService service, SnapshotSpan span, - ArrayBuilder textChanges, ArrayBuilder trackingSpans, CancellationToken cancellationToken) + ICommentSelectionService service, SnapshotSpan span, + ArrayBuilder textChanges, ArrayBuilder trackingSpans) { var (firstLine, lastLine) = DetermineFirstAndLastLine(span); @@ -121,7 +122,7 @@ private static void CommentSpan( } // Get the information from the language as to how they'd like to comment this region. - var commentInfo = service.GetInfoAsync(document, span.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); + var commentInfo = service.GetInfo(); if (!commentInfo.SupportsBlockComment && !commentInfo.SupportsSingleLineComment) { // Neither type of comment supported. @@ -186,10 +187,10 @@ private static void AddBlockComment(SnapshotSpan span, ArrayBuilder /// Add the necessary edits to uncomment out a single span. /// private static void UncommentSpan( - Document document, ICommentSelectionService service, SnapshotSpan span, - ArrayBuilder textChanges, ArrayBuilder spansToSelect, CancellationToken cancellationToken) + ICommentSelectionService service, SnapshotSpan span, + ArrayBuilder textChanges, ArrayBuilder spansToSelect) { - var info = service.GetInfoAsync(document, span.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); + var info = service.GetInfo(); // If the selection is exactly a block comment, use it as priority over single line comments. if (info.SupportsBlockComment && TryUncommentExactlyBlockComment(info, span, textChanges, spansToSelect)) diff --git a/src/EditorFeatures/Core/CommentSelection/ToggleBlockCommentCommandHandler.cs b/src/EditorFeatures/Core/CommentSelection/ToggleBlockCommentCommandHandler.cs index d545ad8c0655d..080615dda86ef 100644 --- a/src/EditorFeatures/Core/CommentSelection/ToggleBlockCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/CommentSelection/ToggleBlockCommentCommandHandler.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; namespace Microsoft.CodeAnalysis.CommentSelection @@ -32,15 +33,15 @@ public ToggleBlockCommentCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, ITextStructureNavigatorSelectorService navigatorSelectorService, - IGlobalOptionService globalOptions) - : base(undoHistoryRegistry, editorOperationsFactoryService, navigatorSelectorService, globalOptions) + EditorOptionsService editorOptionsService) + : base(undoHistoryRegistry, editorOperationsFactoryService, navigatorSelectorService, editorOptionsService) { } /// /// Gets block comments by parsing the text for comment markers. /// - protected override Task> GetBlockCommentsInDocumentAsync(Document document, ITextSnapshot snapshot, + protected override ImmutableArray GetBlockCommentsInDocument(Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken) { var allText = snapshot.AsText(); @@ -62,7 +63,7 @@ protected override Task> GetBlockCommentsInDocumentAsyn openIdx = closeIdx; } - return Task.FromResult(commentedSpans.ToImmutableAndFree()); + return commentedSpans.ToImmutableAndFree(); } } } diff --git a/src/EditorFeatures/Core/CommentSelection/ToggleLineCommentCommandHandler.cs b/src/EditorFeatures/Core/CommentSelection/ToggleLineCommentCommandHandler.cs index 950b314a00776..5b5a8d60201e6 100644 --- a/src/EditorFeatures/Core/CommentSelection/ToggleLineCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/CommentSelection/ToggleLineCommentCommandHandler.cs @@ -45,8 +45,8 @@ internal class ToggleLineCommentCommandHandler : public ToggleLineCommentCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) - : base(undoHistoryRegistry, editorOperationsFactoryService, globalOptions) + EditorOptionsService editorOptionsService) + : base(undoHistoryRegistry, editorOperationsFactoryService, editorOptionsService) { } @@ -62,7 +62,7 @@ public bool ExecuteCommand(ToggleLineCommentCommandArgs args, CommandExecutionCo protected override string GetMessage(ValueTuple command) => EditorFeaturesResources.Toggling_line_comment; - internal override async Task CollectEditsAsync(Document document, ICommentSelectionService service, + internal override CommentSelectionResult CollectEdits(Document document, ICommentSelectionService service, ITextBuffer subjectBuffer, NormalizedSnapshotSpanCollection selectedSpans, ValueTuple command, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.CommandHandler_ToggleLineComment, KeyValueLogMessage.Create(LogType.UserAction, m => @@ -71,7 +71,7 @@ internal override async Task CollectEditsAsync(Document m[LengthString] = subjectBuffer.CurrentSnapshot.Length; }), cancellationToken)) { - var commentInfo = await service.GetInfoAsync(document, selectedSpans.First().Span.ToTextSpan(), cancellationToken).ConfigureAwait(false); + var commentInfo = service.GetInfo(); if (commentInfo.SupportsSingleLineComment) { return ToggleLineComment(commentInfo, selectedSpans); diff --git a/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs index 147ab01f6e250..e76db95b52662 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs @@ -36,7 +36,7 @@ internal partial class DiagnosticsClassificationTaggerProvider : AbstractDiagnos private readonly ClassificationTypeMap _typeMap; private readonly ClassificationTag _classificationTag; - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; protected override IEnumerable> Options => s_tagSourceOptions; @@ -46,21 +46,20 @@ public DiagnosticsClassificationTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, ClassificationTypeMap typeMap, - IEditorOptionsFactoryService editorOptionsFactoryService, - IGlobalOptionService globalOptions, + EditorOptionsService editorOptionsService, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification)) + : base(threadingContext, diagnosticService, editorOptionsService.GlobalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification)) { _typeMap = typeMap; _classificationTag = new ClassificationTag(_typeMap.GetClassificationType(ClassificationTypeDefinitions.UnnecessaryCode)); - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; } // If we are under high contrast mode, the editor ignores classification tags that fade things out, // because that reduces contrast. Since the editor will ignore them, there's no reason to produce them. protected internal override bool IsEnabled - => !_editorOptionsFactoryService.GlobalOptions.GetOptionValue(DefaultTextViewHostOptions.IsInContrastModeId); + => !_editorOptionsService.Factory.GlobalOptions.GetOptionValue(DefaultTextViewHostOptions.IsInContrastModeId); protected internal override bool IncludeDiagnostic(DiagnosticData data) => data.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary); diff --git a/src/EditorFeatures/Core/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs b/src/EditorFeatures/Core/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs index 1f4e7eb6da94d..da88de4df611e 100644 --- a/src/EditorFeatures/Core/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs @@ -30,13 +30,13 @@ internal abstract class AbstractDocumentationCommentCommandHandler : private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; - private readonly IGlobalOptionService _globalOptions; + private readonly EditorOptionsService _editorOptionsService; protected AbstractDocumentationCommentCommandHandler( IUIThreadOperationExecutor uiThreadOperationExecutor, ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, - IGlobalOptionService globalOptions) + EditorOptionsService editorOptionsService) { Contract.ThrowIfNull(uiThreadOperationExecutor); Contract.ThrowIfNull(undoHistoryRegistry); @@ -45,7 +45,7 @@ protected AbstractDocumentationCommentCommandHandler( _uiThreadOperationExecutor = uiThreadOperationExecutor; _undoHistoryRegistry = undoHistoryRegistry; _editorOperationsFactoryService = editorOperationsFactoryService; - _globalOptions = globalOptions; + _editorOptionsService = editorOptionsService; } protected abstract string ExteriorTriviaText { get; } @@ -92,16 +92,15 @@ private bool CompleteComment( } var service = document.GetRequiredLanguageService(); - var syntaxTree = document.GetRequiredSyntaxTreeSynchronously(cancellationToken); - var text = syntaxTree.GetText(cancellationToken); - var options = document.GetDocumentationCommentOptionsAsync(_globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var options = subjectBuffer.GetDocumentationCommentOptions(_editorOptionsService, document.Project.LanguageServices); // Apply snippet in reverse order so that the first applied snippet doesn't affect span of next snippets. var snapshots = textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer).OrderByDescending(s => s.Span.Start); var returnValue = false; foreach (var snapshot in snapshots) { - var snippet = getSnippetAction(service, syntaxTree, text, snapshot.Span.Start, options, cancellationToken); + var snippet = getSnippetAction(service, parsedDocument.SyntaxTree, parsedDocument.Text, snapshot.Span.Start, options, cancellationToken); if (snippet != null) { ApplySnippet(snippet, subjectBuffer, textView); @@ -170,7 +169,7 @@ public bool ExecuteCommand(ReturnKeyCommandArgs args, CommandExecutionContext co return false; } - if (!CurrentLineStartsWithExteriorTrivia(args.SubjectBuffer, originalPosition)) + if (!CurrentLineStartsWithExteriorTrivia(args.SubjectBuffer, originalPosition, context.OperationContext.UserCancellationToken)) { return false; } @@ -246,7 +245,7 @@ public void ExecuteCommand(OpenLineAboveCommandArgs args, Action nextHandler, Co return; } - if (!CurrentLineStartsWithExteriorTrivia(subjectBuffer, caretPosition)) + if (!CurrentLineStartsWithExteriorTrivia(subjectBuffer, caretPosition, context.OperationContext.UserCancellationToken)) { nextHandler(); return; @@ -263,7 +262,7 @@ public void ExecuteCommand(OpenLineAboveCommandArgs args, Action nextHandler, Co var service = document.GetRequiredLanguageService(); - InsertExteriorTriviaIfNeeded(service, args.TextView, subjectBuffer); + InsertExteriorTriviaIfNeeded(service, args.TextView, subjectBuffer, context.OperationContext.UserCancellationToken); } public CommandState GetCommandState(OpenLineBelowCommandArgs args, Func nextHandler) @@ -282,7 +281,7 @@ public void ExecuteCommand(OpenLineBelowCommandArgs args, Action nextHandler, Co return; } - if (!CurrentLineStartsWithExteriorTrivia(subjectBuffer, caretPosition)) + if (!CurrentLineStartsWithExteriorTrivia(subjectBuffer, caretPosition, context.OperationContext.UserCancellationToken)) { nextHandler(); return; @@ -299,10 +298,10 @@ public void ExecuteCommand(OpenLineBelowCommandArgs args, Action nextHandler, Co // Allow nextHandler() to run and the insert exterior trivia if necessary. nextHandler(); - InsertExteriorTriviaIfNeeded(service, args.TextView, subjectBuffer); + InsertExteriorTriviaIfNeeded(service, args.TextView, subjectBuffer, context.OperationContext.UserCancellationToken); } - private void InsertExteriorTriviaIfNeeded(IDocumentationCommentSnippetService service, ITextView textView, ITextBuffer subjectBuffer) + private void InsertExteriorTriviaIfNeeded(IDocumentationCommentSnippetService service, ITextView textView, ITextBuffer subjectBuffer, CancellationToken cancellationToken) { var caretPosition = textView.GetCaretPoint(subjectBuffer) ?? -1; if (caretPosition < 0) @@ -316,27 +315,25 @@ private void InsertExteriorTriviaIfNeeded(IDocumentationCommentSnippetService se return; } - var text = document - .GetTextAsync(CancellationToken.None) - .WaitAndGetResult(CancellationToken.None); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); // We only insert exterior trivia if the current line does not start with exterior trivia // and the previous line does. - var currentLine = text.Lines.GetLineFromPosition(caretPosition); + var currentLine = parsedDocument.Text.Lines.GetLineFromPosition(caretPosition); if (currentLine.LineNumber <= 0) { return; } - var previousLine = text.Lines[currentLine.LineNumber - 1]; + var previousLine = parsedDocument.Text.Lines[currentLine.LineNumber - 1]; if (LineStartsWithExteriorTrivia(currentLine) || !LineStartsWithExteriorTrivia(previousLine)) { return; } - var options = document.GetDocumentationCommentOptionsAsync(_globalOptions, CancellationToken.None).AsTask().WaitAndGetResult(CancellationToken.None); + var options = subjectBuffer.GetDocumentationCommentOptions(_editorOptionsService, document.Project.LanguageServices); var snippet = service.GetDocumentationCommentSnippetFromPreviousLine(options, currentLine, previousLine); if (snippet != null) @@ -345,7 +342,7 @@ private void InsertExteriorTriviaIfNeeded(IDocumentationCommentSnippetService se } } - private bool CurrentLineStartsWithExteriorTrivia(ITextBuffer subjectBuffer, int position) + private bool CurrentLineStartsWithExteriorTrivia(ITextBuffer subjectBuffer, int position, CancellationToken cancellationToken) { var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) @@ -353,11 +350,8 @@ private bool CurrentLineStartsWithExteriorTrivia(ITextBuffer subjectBuffer, int return false; } - var text = document - .GetTextAsync(CancellationToken.None) - .WaitAndGetResult(CancellationToken.None); - - var currentLine = text.Lines.GetLineFromPosition(position); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var currentLine = parsedDocument.Text.Lines.GetLineFromPosition(position); return LineStartsWithExteriorTrivia(currentLine); } diff --git a/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs b/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs index 5022e9b1a1ecb..b63fb190f9c33 100644 --- a/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs +++ b/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs @@ -80,17 +80,17 @@ private bool ExecuteCommandImpl(EditorCommandArgs args, bool gotoNextMember, Com } var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document?.SupportsSyntaxTree != true) + var syntaxFactsService = document?.GetLanguageService(); + if (syntaxFactsService == null) { return false; } int? targetPosition = null; - using (context.OperationContext.AddScope(allowCancellation: true, description: EditorFeaturesResources.Navigating)) { - var task = GetTargetPositionAsync(document, caretPoint.Value.Position, gotoNextMember, context.OperationContext.UserCancellationToken); - targetPosition = task.WaitAndGetResult(context.OperationContext.UserCancellationToken); + var root = document.GetSyntaxRootSynchronously(context.OperationContext.UserCancellationToken); + targetPosition = GetTargetPosition(syntaxFactsService, root, caretPoint.Value.Position, gotoNextMember); } if (targetPosition != null) @@ -104,16 +104,9 @@ private bool ExecuteCommandImpl(EditorCommandArgs args, bool gotoNextMember, Com /// /// Internal for testing purposes. /// - internal static async Task GetTargetPositionAsync(Document document, int caretPosition, bool next, CancellationToken cancellationToken) + internal static int? GetTargetPosition(ISyntaxFactsService service, SyntaxNode root, int caretPosition, bool next) { - var syntaxFactsService = document.GetLanguageService(); - if (syntaxFactsService == null) - { - return null; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(true); - var members = syntaxFactsService.GetMethodLevelMembers(root); + var members = service.GetMethodLevelMembers(root); if (members.Count == 0) { return null; diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs index 088612034526f..4dea901aec400 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs @@ -88,6 +88,7 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive); var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan(); + // Note: C# always completes synchronously, TypeScript is async var changes = formattingService.GetFormattingChangesOnPasteAsync(document, args.SubjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken); if (changes.IsEmpty) { diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs index 9405b0b68e399..ce9b578b312a9 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Linq; using System.Threading; @@ -67,24 +68,26 @@ private void Format(ITextView textView, ITextBuffer textBuffer, Document documen using (Logger.LogBlock(FunctionId.CommandHandler_FormatCommand, KeyValueLogMessage.Create(LogType.UserAction, m => m["Span"] = selectionOpt?.Length ?? -1), cancellationToken)) using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Formatting)) { + // Note: C# always completes synchronously, TypeScript is async var changes = formattingService.GetFormattingChangesAsync(document, textBuffer, selectionOpt, cancellationToken).WaitAndGetResult(cancellationToken); if (changes.IsEmpty) { return; } - ApplyChanges(document, changes, selectionOpt, cancellationToken); + var workspace = document.Project.Solution.Workspace; + ApplyChanges(workspace, document.Id, changes, selectionOpt, cancellationToken); transaction.Complete(); } } - private static void ApplyChanges(Document document, IList changes, TextSpan? selectionOpt, CancellationToken cancellationToken) + private static void ApplyChanges(Workspace workspace, DocumentId documentId, IList changes, TextSpan? selectionOpt, CancellationToken cancellationToken) { if (selectionOpt.HasValue) { - var ruleFactory = document.Project.Solution.Workspace.Services.GetRequiredService(); + var ruleFactory = workspace.Services.GetRequiredService(); - changes = ruleFactory.FilterFormattedChanges(document.Id, selectionOpt.Value, changes).ToList(); + changes = ruleFactory.FilterFormattedChanges(documentId, selectionOpt.Value, changes).ToList(); if (changes.Count == 0) { return; @@ -93,7 +96,7 @@ private static void ApplyChanges(Document document, IList changes, T using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken)) { - document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); + workspace.ApplyTextChanges(documentId, changes, cancellationToken); } } @@ -161,6 +164,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati return; } + // Note: C# always completes synchronously, TypeScript is async textChanges = service.GetFormattingChangesOnReturnAsync(document, caretPosition.Value, cancellationToken).WaitAndGetResult(cancellationToken); } else if (args is TypeCharCommandArgs typeCharArgs) @@ -170,6 +174,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati return; } + // Note: C# always completes synchronously, TypeScript is async textChanges = service.GetFormattingChangesAsync( document, typeCharArgs.SubjectBuffer, typeCharArgs.TypedChar, caretPosition.Value, cancellationToken).WaitAndGetResult(cancellationToken); } diff --git a/src/EditorFeatures/Core/Formatting/IndentationManagerExtensions.cs b/src/EditorFeatures/Core/Formatting/IndentationManagerExtensions.cs deleted file mode 100644 index 35f97053a2ff6..0000000000000 --- a/src/EditorFeatures/Core/Formatting/IndentationManagerExtensions.cs +++ /dev/null @@ -1,41 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.CodeAnalysis.Formatting -{ - internal static class IndentationManagerExtensions - { - public static SyntaxFormattingOptions GetInferredFormattingOptions( - this IIndentationManagerService indentationManager, - ITextBuffer textBuffer, - IEditorOptionsFactoryService editorOptionsFactory, - HostLanguageServices languageServices, - SyntaxFormattingOptions fallbackOptions, - bool explicitFormat) - { - var configOptions = new EditorAnalyzerConfigOptions(editorOptionsFactory.GetOptions(textBuffer)); - var options = configOptions.GetSyntaxFormattingOptions(fallbackOptions, languageServices); - - indentationManager.GetIndentation(textBuffer, explicitFormat, out var convertTabsToSpaces, out var tabSize, out var indentSize); - - return options.With(new LineFormattingOptions() - { - UseTabs = !convertTabsToSpaces, - IndentationSize = indentSize, - TabSize = tabSize, - NewLine = options.NewLine - }); - } - } -} diff --git a/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler.cs b/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler.cs index 908ed357152ff..d8f1718d7fe18 100644 --- a/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler.cs +++ b/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler.cs @@ -4,8 +4,10 @@ using System; using System.Linq; +using Microsoft.CodeAnalysis.Editor.BackgroundWorkIndicator; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -17,13 +19,16 @@ internal abstract partial class AbstractRenameCommandHandler { private readonly IThreadingContext _threadingContext; private readonly InlineRenameService _renameService; + private readonly IAsynchronousOperationListener _listener; protected AbstractRenameCommandHandler( IThreadingContext threadingContext, - InlineRenameService renameService) + InlineRenameService renameService, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider) { _threadingContext = threadingContext; _renameService = renameService; + _listener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.Rename); } public string DisplayName => EditorFeaturesResources.Rename; diff --git a/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler_RenameHandler.cs b/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler_RenameHandler.cs index d762156c45e02..64ec599ba670f 100644 --- a/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler_RenameHandler.cs +++ b/src/EditorFeatures/Core/InlineRename/CommandHandlers/AbstractRenameCommandHandler_RenameHandler.cs @@ -4,12 +4,19 @@ #nullable disable +using System; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.BackgroundWorkIndicator; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename { @@ -38,16 +45,15 @@ public bool ExecuteCommand(RenameCommandArgs args, CommandExecutionContext conte return false; } - using (context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Finding_token_to_rename)) - { - ExecuteRenameWorker(args, context); - } - + var token = _listener.BeginAsyncOperation(nameof(ExecuteCommand)); + _ = ExecuteCommandAsync(args).CompletesAsyncOperation(token); return true; } - private void ExecuteRenameWorker(RenameCommandArgs args, CommandExecutionContext context) + private async Task ExecuteCommandAsync(RenameCommandArgs args) { + _threadingContext.ThrowIfNotOnUIThread(); + if (!args.SubjectBuffer.TryGetWorkspace(out var workspace)) { return; @@ -56,10 +62,16 @@ private void ExecuteRenameWorker(RenameCommandArgs args, CommandExecutionContext var caretPoint = args.TextView.GetCaretPoint(args.SubjectBuffer); if (!caretPoint.HasValue) { - ShowErrorDialog(workspace, EditorFeaturesResources.You_must_rename_an_identifier); + await ShowErrorDialogAsync(workspace, EditorFeaturesResources.You_must_rename_an_identifier).ConfigureAwait(false); return; } + var backgroundWorkIndicatorFactory = workspace.Services.GetRequiredService(); + using var context = backgroundWorkIndicatorFactory.Create( + args.TextView, + args.TextView.GetTextElementSpan(caretPoint.Value), + EditorFeaturesResources.Finding_token_to_rename); + // If there is already an active session, commit it first if (_renameService.ActiveSession != null) { @@ -77,12 +89,17 @@ private void ExecuteRenameWorker(RenameCommandArgs args, CommandExecutionContext } } - var cancellationToken = context.OperationContext.UserCancellationToken; - var document = args.SubjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChanges( - context.OperationContext, _threadingContext); + var cancellationToken = context.UserCancellationToken; + + var document = await args + .SubjectBuffer + .CurrentSnapshot + .GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync(context) + .ConfigureAwait(false); + if (document == null) { - ShowErrorDialog(workspace, EditorFeaturesResources.You_must_rename_an_identifier); + await ShowErrorDialogAsync(workspace, EditorFeaturesResources.You_must_rename_an_identifier).ConfigureAwait(false); return; } @@ -92,14 +109,15 @@ private void ExecuteRenameWorker(RenameCommandArgs args, CommandExecutionContext // There can be zero selectedSpans in projection scenarios. if (selectedSpans.Count != 1) { - ShowErrorDialog(workspace, EditorFeaturesResources.You_must_rename_an_identifier); + await ShowErrorDialogAsync(workspace, EditorFeaturesResources.You_must_rename_an_identifier).ConfigureAwait(false); return; } - var sessionInfo = _renameService.StartInlineSession(document, selectedSpans.Single().Span.ToTextSpan(), cancellationToken); + var sessionInfo = await _renameService.StartInlineSessionAsync(document, selectedSpans.Single().Span.ToTextSpan(), cancellationToken).ConfigureAwait(false); if (!sessionInfo.CanRename) { - ShowErrorDialog(workspace, sessionInfo.LocalizedErrorMessage); + await ShowErrorDialogAsync(workspace, sessionInfo.LocalizedErrorMessage).ConfigureAwait(false); + return; } } @@ -110,8 +128,9 @@ private static bool CanRename(RenameCommandArgs args) args.SubjectBuffer.SupportsRename() && !args.SubjectBuffer.IsInLspEditorContext(); } - private static void ShowErrorDialog(Workspace workspace, string message) + private async Task ShowErrorDialogAsync(Workspace workspace, string message) { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); var notificationService = workspace.Services.GetService(); notificationService.SendNotification(message, title: EditorFeaturesResources.Rename, severity: NotificationSeverity.Error); } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs index ada565c309834..7d1f65f137ba9 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameService.cs @@ -68,7 +68,7 @@ public InlineRenameSessionInfo StartInlineSession( return _threadingContext.JoinableTaskFactory.Run(() => StartInlineSessionAsync(document, textSpan, cancellationToken)); } - private async Task StartInlineSessionAsync( + public async Task StartInlineSessionAsync( Document document, TextSpan textSpan, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 824b5edd7bfc4..3a811b0c009f3 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -9,12 +9,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers.Snippets; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Indentation; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -38,6 +38,7 @@ internal sealed class CommitManager : IAsyncCompletionCommitManager private readonly ITextView _textView; private readonly IGlobalOptionService _globalOptions; private readonly IThreadingContext _threadingContext; + private readonly ILanguageServerSnippetExpander? _languageServerSnippetExpander; public IEnumerable PotentialCommitCharacters { @@ -59,12 +60,14 @@ internal CommitManager( ITextView textView, RecentItemsManager recentItemsManager, IGlobalOptionService globalOptions, - IThreadingContext threadingContext) + IThreadingContext threadingContext, + ILanguageServerSnippetExpander? languageServerSnippetExpander) { _globalOptions = globalOptions; _threadingContext = threadingContext; _recentItemsManager = recentItemsManager; _textView = textView; + _languageServerSnippetExpander = languageServerSnippetExpander; } /// @@ -229,6 +232,23 @@ private AsyncCompletionData.CommitResult Commit( var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); var mappedSpan = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + // Specifically for snippets, we check to see if the associated completion item is a snippet, + // and if so, we call upon the LanguageServerSnippetExpander's TryExpand to insert the snippet. + if (SnippetCompletionItem.IsSnippet(roslynItem)) + { + Contract.ThrowIfNull(_languageServerSnippetExpander); + + var lspSnippetText = change.Properties[SnippetCompletionItem.LSPSnippetKey]; + + Contract.ThrowIfNull(lspSnippetText); + if (!_languageServerSnippetExpander.TryExpand(lspSnippetText, mappedSpan, _textView)) + { + FatalError.ReportAndCatch(new InvalidOperationException("The invoked LSP snippet expander came back as false."), ErrorSeverity.Critical); + } + + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); + } + using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) { edit.Replace(mappedSpan.Span, change.TextChange.NewText); @@ -276,8 +296,9 @@ private AsyncCompletionData.CommitResult Commit( if (currentDocument != null && formattingService != null) { var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); - var changes = formattingService.GetFormattingChangesAsync( - currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); + + // Note: C# always completes synchronously, TypeScript is async + var changes = formattingService.GetFormattingChangesAsync(currentDocument, subjectBuffer, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken); } } diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs index 0e26037c4397d..61d259cd93922 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs @@ -22,17 +22,20 @@ internal class CommitManagerProvider : IAsyncCompletionCommitManagerProvider private readonly IThreadingContext _threadingContext; private readonly RecentItemsManager _recentItemsManager; private readonly IGlobalOptionService _globalOptions; + private readonly ILanguageServerSnippetExpander? _languageServerSnippetExpander; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CommitManagerProvider( IThreadingContext threadingContext, RecentItemsManager recentItemsManager, - IGlobalOptionService globalOptions) + IGlobalOptionService globalOptions, + [Import(AllowDefault = true)] ILanguageServerSnippetExpander? languageServerSnippetExpander) { _threadingContext = threadingContext; _recentItemsManager = recentItemsManager; _globalOptions = globalOptions; + _languageServerSnippetExpander = languageServerSnippetExpander; } IAsyncCompletionCommitManager? IAsyncCompletionCommitManagerProvider.GetOrCreate(ITextView textView) @@ -42,7 +45,7 @@ public CommitManagerProvider( return null; } - return new CommitManager(textView, _recentItemsManager, _globalOptions, _threadingContext); + return new CommitManager(textView, _recentItemsManager, _globalOptions, _threadingContext, _languageServerSnippetExpander); } } } diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ILanguageServerSnippetExpander.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ILanguageServerSnippetExpander.cs new file mode 100644 index 0000000000000..bdfe02aa3816b --- /dev/null +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ILanguageServerSnippetExpander.cs @@ -0,0 +1,14 @@ +// 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; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion +{ + internal interface ILanguageServerSnippetExpander + { + bool TryExpand(string lspSnippetText, SnapshotSpan snapshotSpan, ITextView textView); + } +} diff --git a/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs b/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs index 8a0048d55543e..34ad60a6c31cd 100644 --- a/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs +++ b/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs @@ -182,10 +182,7 @@ private void OnModelUpdated(TModel result, bool updateController) this.ThreadingContext.ThrowIfNotOnUIThread(); // Store the first result so that anyone who cares knows we've computed something - if (_initialUnfilteredModel == null) - { - _initialUnfilteredModel = result; - } + _initialUnfilteredModel ??= result; _controller.OnModelUpdated(result, updateController); } diff --git a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs index d457f271f1e32..26cad7274c8fa 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs @@ -4,7 +4,6 @@ extern alias InteractiveHost; extern alias Scripting; - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -17,11 +16,13 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Interactive @@ -37,6 +38,7 @@ internal sealed class InteractiveSession : IDisposable private readonly InteractiveEvaluatorLanguageInfoProvider _languageInfo; private readonly InteractiveWorkspace _workspace; private readonly ITextDocumentFactoryService _textDocumentFactoryService; + private readonly EditorOptionsService _editorOptionsService; private readonly CancellationTokenSource _shutdownCancellationSource; @@ -77,14 +79,16 @@ public InteractiveSession( InteractiveWorkspace workspace, IThreadingContext threadingContext, IAsynchronousOperationListener listener, - ITextDocumentFactoryService factoryService, + ITextDocumentFactoryService documentFactory, + EditorOptionsService editorOptionsService, InteractiveEvaluatorLanguageInfoProvider languageInfo, string initialWorkingDirectory) { _workspace = workspace; _threadingContext = threadingContext; _languageInfo = languageInfo; - _textDocumentFactoryService = factoryService; + _textDocumentFactoryService = documentFactory; + _editorOptionsService = editorOptionsService; _taskQueue = new TaskQueue(listener, TaskScheduler.Default); _shutdownCancellationSource = new CancellationTokenSource(); diff --git a/src/EditorFeatures/Core/Interactive/SendToInteractiveSubmissionProvider.cs b/src/EditorFeatures/Core/Interactive/SendToInteractiveSubmissionProvider.cs index ae3f4c7138782..7daccf0b885f1 100644 --- a/src/EditorFeatures/Core/Interactive/SendToInteractiveSubmissionProvider.cs +++ b/src/EditorFeatures/Core/Interactive/SendToInteractiveSubmissionProvider.cs @@ -37,20 +37,20 @@ internal abstract class AbstractSendToInteractiveSubmissionProvider : ISendToInt string ISendToInteractiveSubmissionProvider.GetSelectedText(IEditorOptions editorOptions, EditorCommandArgs args, CancellationToken cancellationToken) { var selectedSpans = args.TextView.Selection.IsEmpty - ? GetExpandedLineAsync(editorOptions, args, cancellationToken).WaitAndGetResult(cancellationToken) + ? GetExpandedLine(editorOptions, args, cancellationToken) : args.TextView.Selection.GetSnapshotSpansOnBuffer(args.SubjectBuffer).Where(ss => ss.Length > 0); return GetSubmissionFromSelectedSpans(editorOptions, selectedSpans); } /// Returns the span for the selected line. Extends it if it is a part of a multi line statement or declaration. - private Task> GetExpandedLineAsync(IEditorOptions editorOptions, EditorCommandArgs args, CancellationToken cancellationToken) + private IEnumerable GetExpandedLine(IEditorOptions editorOptions, EditorCommandArgs args, CancellationToken cancellationToken) { var selectedSpans = GetSelectedLine(args.TextView); var candidateSubmission = GetSubmissionFromSelectedSpans(editorOptions, selectedSpans); return CanParseSubmission(candidateSubmission) - ? Task.FromResult(selectedSpans) - : ExpandSelectionAsync(selectedSpans, args, cancellationToken); + ? selectedSpans + : ExpandSelection(selectedSpans, args, cancellationToken); } /// Returns the span for the currently selected line. @@ -61,35 +61,19 @@ private static IEnumerable GetSelectedLine(ITextView textView) return new NormalizedSnapshotSpanCollection(span); } - private async Task> GetExecutableSyntaxTreeNodeSelectionAsync( - TextSpan selectionSpan, - EditorCommandArgs args, - ITextSnapshot snapshot, - CancellationToken cancellationToken) - { - var doc = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - var semanticDocument = await SemanticDocument.CreateAsync(doc, cancellationToken).ConfigureAwait(false); - var root = semanticDocument.Root; - - return GetExecutableSyntaxTreeNodeSelection(selectionSpan, root) - .Select(span => new SnapshotSpan(snapshot, span.Start, span.Length)); - } - - private async Task> ExpandSelectionAsync(IEnumerable selectedSpans, EditorCommandArgs args, CancellationToken cancellationToken) + private IEnumerable ExpandSelection(IEnumerable selectedSpans, EditorCommandArgs args, CancellationToken cancellationToken) { var selectedSpansStart = selectedSpans.Min(span => span.Start); var selectedSpansEnd = selectedSpans.Max(span => span.End); var snapshot = args.TextView.TextSnapshot; - var newSpans = await GetExecutableSyntaxTreeNodeSelectionAsync( - TextSpan.FromBounds(selectedSpansStart, selectedSpansEnd), - args, - snapshot, - cancellationToken).ConfigureAwait(false); + var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + var root = document.GetSyntaxRootSynchronously(cancellationToken); + + var newSpans = GetExecutableSyntaxTreeNodeSelection(TextSpan.FromBounds(selectedSpansStart, selectedSpansEnd), root). + Select(span => new SnapshotSpan(snapshot, span.Start, span.Length)); - return newSpans.Any() - ? newSpans.Select(n => new SnapshotSpan(snapshot, n.Span.Start, n.Span.Length)) - : selectedSpans; + return newSpans.Any() ? newSpans : selectedSpans; } private static string GetSubmissionFromSelectedSpans(IEditorOptions editorOptions, IEnumerable selectedSpans) diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/Completion/CompletionHandler.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/Completion/CompletionHandler.cs index 85451ddbb0ee9..4f64ad98e2acf 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/Completion/CompletionHandler.cs +++ b/src/EditorFeatures/Core/LanguageServer/Handlers/Completion/CompletionHandler.cs @@ -338,12 +338,9 @@ static void PromoteCommonCommitCharactersOntoList(LSP.VSInternalCompletionList c { var completionItem = completionList.Items[i]; var commitCharacters = completionItem.CommitCharacters; - if (commitCharacters == null) - { - // The commit characters on the item are null, this means the commit characters are actually - // the default commit characters we passed in the initialize request. - commitCharacters = defaultCommitCharacters; - } + // The commit characters on the item are null, this means the commit characters are actually + // the default commit characters we passed in the initialize request. + commitCharacters ??= defaultCommitCharacters; commitCharacterReferences.TryGetValue(commitCharacters, out var existingCount); existingCount++; diff --git a/src/EditorFeatures/Core/Options/EditorAnalyzerConfigOptions.cs b/src/EditorFeatures/Core/Options/EditorAnalyzerConfigOptions.cs index c0aca642e0ec4..1952af29dc142 100644 --- a/src/EditorFeatures/Core/Options/EditorAnalyzerConfigOptions.cs +++ b/src/EditorFeatures/Core/Options/EditorAnalyzerConfigOptions.cs @@ -15,11 +15,11 @@ namespace Microsoft.CodeAnalysis.Diagnostics { internal sealed class EditorAnalyzerConfigOptions : AnalyzerConfigOptions { - private readonly IDictionary _options; + private readonly IDictionary _configOptions; public EditorAnalyzerConfigOptions(IEditorOptions editorOptions) { - _options = (editorOptions.GetOptionValue(DefaultOptions.RawCodingConventionsSnapshotOptionName) as IDictionary) ?? + _configOptions = (editorOptions.GetOptionValue(DefaultOptions.RawCodingConventionsSnapshotOptionName) as IDictionary) ?? SpecializedCollections.EmptyDictionary(); } @@ -28,7 +28,7 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val // TODO: the editor currently seems to store both lower-cased keys and original casing, the comparer is case-sensitive // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1556206 - if (_options.TryGetValue(key.ToLowerInvariant(), out var objectValue)) + if (_configOptions.TryGetValue(key.ToLowerInvariant(), out var objectValue)) { // TODO: Although the editor exposes values typed to object they are actually strings. value = objectValue switch @@ -46,7 +46,7 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val } public override IEnumerable Keys - => _options.Keys.Where(IsLowercase); + => _configOptions.Keys.Where(IsLowercase); private static bool IsLowercase(string str) { diff --git a/src/EditorFeatures/Core/Options/EditorOptionsService.cs b/src/EditorFeatures/Core/Options/EditorOptionsService.cs new file mode 100644 index 0000000000000..6949a57b43465 --- /dev/null +++ b/src/EditorFeatures/Core/Options/EditorOptionsService.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.Composition; +using System.Text; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.Options; + +/// +/// Aggregates services necessary to retrieve editor options. +/// +[Export(typeof(EditorOptionsService)), Shared] +internal sealed class EditorOptionsService +{ + public readonly IGlobalOptionService GlobalOptions; + public readonly IEditorOptionsFactoryService Factory; + public readonly IIndentationManagerService IndentationManager; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public EditorOptionsService(IGlobalOptionService globalOptions, IEditorOptionsFactoryService factory, IIndentationManagerService indentationManager) + { + GlobalOptions = globalOptions; + Factory = factory; + IndentationManager = indentationManager; + } +} diff --git a/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs b/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs new file mode 100644 index 0000000000000..488c08f4dbf6a --- /dev/null +++ b/src/EditorFeatures/Core/Options/TextBufferOptionProviders.cs @@ -0,0 +1,85 @@ +// 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.Text; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.DocumentationComments; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Indentation; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; + +namespace Microsoft.CodeAnalysis.Options; + +internal static class TextBufferOptionProviders +{ + public static DocumentationCommentOptions GetDocumentationCommentOptions(this ITextBuffer textBuffer, EditorOptionsService optionsProvider, HostLanguageServices languageServices) + { + var editorOptions = optionsProvider.Factory.GetOptions(textBuffer); + var lineFormattingOptions = GetLineFormattingOptionsImpl(textBuffer, editorOptions, optionsProvider.IndentationManager, explicitFormat: false); + return optionsProvider.GlobalOptions.GetDocumentationCommentOptions(lineFormattingOptions, languageServices.Language); + } + + public static LineFormattingOptions GetLineFormattingOptions(this ITextBuffer textBuffer, EditorOptionsService optionsProvider, bool explicitFormat) + => GetLineFormattingOptionsImpl(textBuffer, optionsProvider.Factory.GetOptions(textBuffer), optionsProvider.IndentationManager, explicitFormat); + + private static LineFormattingOptions GetLineFormattingOptionsImpl(ITextBuffer textBuffer, IEditorOptions editorOptions, IIndentationManagerService indentationManager, bool explicitFormat) + { + indentationManager.GetIndentation(textBuffer, explicitFormat, out var convertTabsToSpaces, out var tabSize, out var indentSize); + + return new LineFormattingOptions() + { + UseTabs = !convertTabsToSpaces, + IndentationSize = indentSize, + TabSize = tabSize, + NewLine = editorOptions.GetNewLineCharacter(), + }; + } + + public static SyntaxFormattingOptions GetSyntaxFormattingOptions(this ITextBuffer textBuffer, EditorOptionsService optionsProvider, HostLanguageServices languageServices, bool explicitFormat) + => GetSyntaxFormattingOptionsImpl(textBuffer, optionsProvider.Factory.GetOptions(textBuffer), optionsProvider.IndentationManager, optionsProvider.GlobalOptions, languageServices, explicitFormat); + + private static SyntaxFormattingOptions GetSyntaxFormattingOptionsImpl(ITextBuffer textBuffer, IEditorOptions editorOptions, IIndentationManagerService indentationManager, IGlobalOptionService globalOptions, HostLanguageServices languageServices, bool explicitFormat) + { + var configOptions = new EditorAnalyzerConfigOptions(editorOptions); + var fallbackOptions = globalOptions.GetSyntaxFormattingOptions(languageServices); + var options = configOptions.GetSyntaxFormattingOptions(fallbackOptions, languageServices); + var lineFormattingOptions = GetLineFormattingOptionsImpl(textBuffer, editorOptions, indentationManager, explicitFormat); + + return options.With(lineFormattingOptions); + } + + public static IndentationOptions GetIndentationOptions(this ITextBuffer textBuffer, EditorOptionsService optionsProvider, HostLanguageServices languageServices, bool explicitFormat) + { + var editorOptions = optionsProvider.Factory.GetOptions(textBuffer); + var formattingOptions = GetSyntaxFormattingOptionsImpl(textBuffer, editorOptions, optionsProvider.IndentationManager, optionsProvider.GlobalOptions, languageServices, explicitFormat); + + return new IndentationOptions(formattingOptions) + { + AutoFormattingOptions = optionsProvider.GlobalOptions.GetAutoFormattingOptions(languageServices.Language), + // TODO: Call editorOptions.GetIndentStyle() instead (see https://github.com/dotnet/roslyn/issues/62204): + IndentStyle = optionsProvider.GlobalOptions.GetOption(IndentationOptionsStorage.SmartIndent, languageServices.Language) + }; + } + + public static IndentingStyle ToEditorIndentStyle(this FormattingOptions2.IndentStyle value) + => value switch + { + FormattingOptions2.IndentStyle.Smart => IndentingStyle.Smart, + FormattingOptions2.IndentStyle.Block => IndentingStyle.Block, + _ => IndentingStyle.None, + }; + + public static FormattingOptions2.IndentStyle ToIndentStyle(this IndentingStyle value) + => value switch + { + IndentingStyle.Smart => FormattingOptions2.IndentStyle.Smart, + IndentingStyle.Block => FormattingOptions2.IndentStyle.Block, + _ => FormattingOptions2.IndentStyle.None, + }; +} diff --git a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs index 111d81672b4c5..294bcb3b6ffe5 100644 --- a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs +++ b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs @@ -38,10 +38,9 @@ internal abstract class AbstractPreviewFactoryService : IPrev private readonly ITextBufferFactoryService _textBufferFactoryService; private readonly IContentTypeRegistryService _contentTypeRegistryService; private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; private readonly ITextDifferencingSelectorService _differenceSelectorService; private readonly IDifferenceBufferFactoryService _differenceBufferService; - private readonly IGlobalOptionService _globalOptions; protected readonly IThreadingContext ThreadingContext; @@ -52,22 +51,19 @@ public AbstractPreviewFactoryService( ITextBufferFactoryService textBufferFactoryService, IContentTypeRegistryService contentTypeRegistryService, IProjectionBufferFactoryService projectionBufferFactoryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, ITextDifferencingSelectorService differenceSelectorService, IDifferenceBufferFactoryService differenceBufferService, - ITextViewRoleSet previewRoleSet, - IGlobalOptionService globalOptions) + ITextViewRoleSet previewRoleSet) { ThreadingContext = threadingContext; _textBufferFactoryService = textBufferFactoryService; _contentTypeRegistryService = contentTypeRegistryService; _projectionBufferFactoryService = projectionBufferFactoryService; - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; _differenceSelectorService = differenceSelectorService; _differenceBufferService = differenceBufferService; - _previewRoleSet = previewRoleSet; - _globalOptions = globalOptions; } public SolutionPreviewResult? GetSolutionPreviews(Solution oldSolution, Solution? newSolution, CancellationToken cancellationToken) @@ -585,7 +581,7 @@ public Task CreateRemovedAnalyzerConfigDocumentPreviewV var originalBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, - _editorOptionsFactoryService.GlobalOptions, + _editorOptionsService.Factory.GlobalOptions, oldBuffer.CurrentSnapshot, "...", description, @@ -593,7 +589,7 @@ public Task CreateRemovedAnalyzerConfigDocumentPreviewV var changedBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, - _editorOptionsFactoryService.GlobalOptions, + _editorOptionsService.Factory.GlobalOptions, newBuffer.CurrentSnapshot, "...", description, @@ -682,7 +678,7 @@ private async ValueTask CreateNewDifferenceViewerAsync( rightWorkspace = null; }; - if (_globalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) + if (_editorOptionsService.GlobalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) { leftWorkspace?.EnableSolutionCrawler(); rightWorkspace?.EnableSolutionCrawler(); diff --git a/src/EditorFeatures/Core/Shared/Extensions/IEditorOptionsFactoryServiceExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/IEditorOptionsFactoryServiceExtensions.cs deleted file mode 100644 index bb6420e22a647..0000000000000 --- a/src/EditorFeatures/Core/Shared/Extensions/IEditorOptionsFactoryServiceExtensions.cs +++ /dev/null @@ -1,47 +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 Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions -{ - internal static class IEditorOptionsFactoryServiceExtensions - { - public static IEditorOptions GetEditorOptions(this IEditorOptionsFactoryService editorOptionsFactory, SourceText text) - { - var textBuffer = text.Container.TryGetTextBuffer(); - if (textBuffer != null) - { - return editorOptionsFactory.GetOptions(textBuffer); - } - - return editorOptionsFactory.GlobalOptions; - } - - public static IEditorOptions GetEditorOptions(this IEditorOptionsFactoryService editorOptionsFactory, Document document) - { - if (document.TryGetText(out var text)) - { - return editorOptionsFactory.GetEditorOptions(text); - } - - return editorOptionsFactory.GlobalOptions; - } - - // This particular section is commented for future reference if there arises a need to implement a option serializer in the editor layer - // public static IOptionService GetFormattingOptions(this IEditorOptionsFactoryService editorOptionsFactory, Document document) - // { - // return CreateOptions(editorOptionsFactory.GetEditorOptions(document)); - // } - - // private static IOptionService CreateOptions(IEditorOptions editorOptions) - // { - // return new FormattingOptions( - // !editorOptions.IsConvertTabsToSpacesEnabled(), - // editorOptions.GetTabSize(), - // editorOptions.GetIndentSize()); - // } - } -} diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs index 27da8122805eb..02efb817ab7af 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; @@ -25,9 +26,13 @@ internal static partial class ITextSnapshotExtensions /// /// format given snapshot and apply text changes to buffer /// - public static void FormatAndApplyToBuffer(this ITextSnapshot snapshot, TextSpan span, IGlobalOptionService globalOptions, CancellationToken cancellationToken) + public static void FormatAndApplyToBuffer( + this ITextBuffer textBuffer, + TextSpan span, + EditorOptionsService editorOptionsService, + CancellationToken cancellationToken) { - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + var document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return; @@ -38,7 +43,7 @@ public static void FormatAndApplyToBuffer(this ITextSnapshot snapshot, TextSpan var formatter = document.GetRequiredLanguageService(); - var options = document.GetSyntaxFormattingOptionsAsync(globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var options = textBuffer.GetSyntaxFormattingOptions(editorOptionsService, document.Project.LanguageServices, explicitFormat: false); var result = formatter.GetFormattingResult(documentSyntax.Root, SpecializedCollections.SingletonEnumerable(span), options, rules, cancellationToken); var changes = result.GetTextChanges(cancellationToken); diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs index 47f7fed907156..b4fd8bae9af11 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextViewExtensions.cs @@ -133,10 +133,7 @@ public static bool TryMoveCaretToAndEnsureVisible(this ITextView textView, Virtu { var outliningManager = outliningManagerService.GetOutliningManager(textView); - if (outliningManager != null) - { - outliningManager.ExpandAll(new SnapshotSpan(pointInView.Value, length: 0), match: _ => true); - } + outliningManager?.ExpandAll(new SnapshotSpan(pointInView.Value, length: 0), match: _ => true); } var newPosition = textView.Caret.MoveTo(new VirtualSnapshotPoint(pointInView.Value, point.VirtualSpaces)); diff --git a/src/EditorFeatures/Core/Shared/Utilities/CaretPreservingEditTransaction.cs b/src/EditorFeatures/Core/Shared/Utilities/CaretPreservingEditTransaction.cs index 95e4622fc52e9..9719cfc3fc891 100644 --- a/src/EditorFeatures/Core/Shared/Utilities/CaretPreservingEditTransaction.cs +++ b/src/EditorFeatures/Core/Shared/Utilities/CaretPreservingEditTransaction.cs @@ -58,10 +58,7 @@ public void Complete() } _editorOperations.AddAfterTextBufferChangePrimitive(); - if (_transaction != null) - { - _transaction.Complete(); - } + _transaction?.Complete(); EndTransaction(); } @@ -73,10 +70,7 @@ public void Cancel() throw new InvalidOperationException(EditorFeaturesResources.The_transaction_is_already_complete); } - if (_transaction != null) - { - _transaction.Cancel(); - } + _transaction?.Cancel(); EndTransaction(); } diff --git a/src/EditorFeatures/Core/SmartIndent/SmartIndent.cs b/src/EditorFeatures/Core/SmartIndent/SmartIndent.cs index b1fbf68c91a66..78bbf9f464afa 100644 --- a/src/EditorFeatures/Core/SmartIndent/SmartIndent.cs +++ b/src/EditorFeatures/Core/SmartIndent/SmartIndent.cs @@ -20,12 +20,12 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.SmartIndent internal partial class SmartIndent : ISmartIndent { private readonly ITextView _textView; - private readonly IGlobalOptionService _globalOptions; + private readonly EditorOptionsService _editorOptionsService; - public SmartIndent(ITextView textView, IGlobalOptionService globalOptions) + public SmartIndent(ITextView textView, EditorOptionsService editorOptionsService) { _textView = textView; - _globalOptions = globalOptions; + _editorOptionsService = editorOptionsService; } public int? GetDesiredIndentation(ITextSnapshotLine line) @@ -50,8 +50,9 @@ public void Dispose() if (newService == null) return null; - var indentationOptions = document.GetIndentationOptionsAsync(_globalOptions, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); - var result = newService.GetIndentation(document, line.LineNumber, indentationOptions, cancellationToken); + var indentationOptions = line.Snapshot.TextBuffer.GetIndentationOptions(_editorOptionsService, document.Project.LanguageServices, explicitFormat: false); + var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); + var result = newService.GetIndentation(parsedDocument, line.LineNumber, indentationOptions, cancellationToken); return result.GetIndentation(_textView, line); } } diff --git a/src/EditorFeatures/Core/SmartIndent/SmartIndentProvider.cs b/src/EditorFeatures/Core/SmartIndent/SmartIndentProvider.cs index 52bab7b222477..304713a70565b 100644 --- a/src/EditorFeatures/Core/SmartIndent/SmartIndentProvider.cs +++ b/src/EditorFeatures/Core/SmartIndent/SmartIndentProvider.cs @@ -18,13 +18,13 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.SmartIndent [ContentType(ContentTypeNames.VisualBasicContentType)] internal sealed class SmartIndentProvider : ISmartIndentProvider { - private readonly IGlobalOptionService _globalOptions; + private readonly EditorOptionsService _editorOptionsService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SmartIndentProvider(IGlobalOptionService globalOptions) + public SmartIndentProvider(EditorOptionsService editorOptionsService) { - _globalOptions = globalOptions; + _editorOptionsService = editorOptionsService; } public ISmartIndent? CreateSmartIndent(ITextView textView) @@ -34,12 +34,12 @@ public SmartIndentProvider(IGlobalOptionService globalOptions) throw new ArgumentNullException(nameof(textView)); } - if (!_globalOptions.GetOption(InternalFeatureOnOffOptions.SmartIndenter)) + if (!_editorOptionsService.GlobalOptions.GetOption(InternalFeatureOnOffOptions.SmartIndenter)) { return null; } - return new SmartIndent(textView, _globalOptions); + return new SmartIndent(textView, _editorOptionsService); } } } diff --git a/src/EditorFeatures/Core/SplitComment/SplitCommentCommandHandler.cs b/src/EditorFeatures/Core/SplitComment/SplitCommentCommandHandler.cs index 20204663b8ed3..94c6e2477cd0d 100644 --- a/src/EditorFeatures/Core/SplitComment/SplitCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/SplitComment/SplitCommentCommandHandler.cs @@ -34,6 +34,8 @@ internal sealed class SplitCommentCommandHandler : ICommandHandler SplitCommentAsync( + private (Span replacementSpan, string replacementText)? SplitComment( + ParsedDocument document, ITextView textView, - Document document, - SnapshotSpan selectionSpan, - CancellationToken cancellationToken) + ITextBuffer textBuffer, + SnapshotSpan selectionSpan) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxKinds = document.GetRequiredLanguageService(); - var trivia = root.FindTrivia(selectionSpan.Start); + var syntaxKinds = document.LanguageServices.GetRequiredService(); + var trivia = document.Root.FindTrivia(selectionSpan.Start); if (syntaxKinds.SingleLineCommentTrivia != trivia.RawKind) return null; - var splitCommentService = document.GetRequiredLanguageService(); + var splitCommentService = document.LanguageServices.GetRequiredService(); // if the user hits enter at `/$$/` we don't want to consider this a comment continuation. if (selectionSpan.Start < (trivia.SpanStart + splitCommentService.CommentStart.Length)) return null; - if (!splitCommentService.IsAllowed(root, trivia)) + if (!splitCommentService.IsAllowed(document.Root, trivia)) return null; // If the user hits enter at: // goo $$ // bar @@ -169,7 +175,7 @@ private static bool MatchesCommentStart(string commentStart, ITextSnapshotLine l var textSnapshot = selectionSpan.Snapshot; var triviaLine = textSnapshot.GetLineFromPosition(trivia.SpanStart); - var options = await document.GetLineFormattingOptionsAsync(_globalOptions, cancellationToken).ConfigureAwait(false); + var options = textBuffer.GetLineFormattingOptions(_editorOptionsService, explicitFormat: false); var replacementSpan = GetReplacementSpan(triviaLine, selectionSpan); var replacementText = GetReplacementText(textView, options, triviaLine, trivia, selectionSpan.Start); return (replacementSpan, replacementText); diff --git a/src/EditorFeatures/Core/Structure/AbstractStructureTaggerProvider.cs b/src/EditorFeatures/Core/Structure/AbstractStructureTaggerProvider.cs index 6db366c746186..0e699d2956b4b 100644 --- a/src/EditorFeatures/Core/Structure/AbstractStructureTaggerProvider.cs +++ b/src/EditorFeatures/Core/Structure/AbstractStructureTaggerProvider.cs @@ -47,19 +47,18 @@ internal abstract partial class AbstractStructureTaggerProvider : private const string ExternDeclaration = "extern"; private const string ImportsStatement = "Imports"; - protected readonly IEditorOptionsFactoryService EditorOptionsFactoryService; + protected readonly EditorOptionsService EditorOptionsService; protected readonly IProjectionBufferFactoryService ProjectionBufferFactoryService; protected AbstractStructureTaggerProvider( IThreadingContext threadingContext, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IProjectionBufferFactoryService projectionBufferFactoryService, - IGlobalOptionService globalOptions, ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Outlining)) + : base(threadingContext, editorOptionsService.GlobalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Outlining)) { - EditorOptionsFactoryService = editorOptionsFactoryService; + EditorOptionsService = editorOptionsService; ProjectionBufferFactoryService = projectionBufferFactoryService; } @@ -351,7 +350,7 @@ private ITextBuffer CreateElisionBufferWithoutIndentation( SnapshotSpan shortHintSpan) { return ProjectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( - EditorOptionsFactoryService.GlobalOptions, + EditorOptionsService.Factory.GlobalOptions, contentType: null, exposedSpans: shortHintSpan); } diff --git a/src/EditorFeatures/Core/Undo/EditorSourceTextUndoService.cs b/src/EditorFeatures/Core/Undo/EditorSourceTextUndoService.cs index 5cac788aec015..2d7768818cd4e 100644 --- a/src/EditorFeatures/Core/Undo/EditorSourceTextUndoService.cs +++ b/src/EditorFeatures/Core/Undo/EditorSourceTextUndoService.cs @@ -93,10 +93,7 @@ internal bool Begin(ITextUndoHistory undoHistory) public void Dispose() { - if (_transaction != null) - { - _transaction.Complete(); - } + _transaction?.Complete(); _service.EndUndoTransaction(this); } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs b/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs index 2a9e0f8425070..79b1e6cd84079 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs @@ -100,10 +100,7 @@ public async Task GetParameterConfigurationAsync() public void Dispose() { - if (Workspace != null) - { - Workspace.Dispose(); - } + Workspace?.Dispose(); } } } diff --git a/src/EditorFeatures/Test/CodeAnalysisResources.cs b/src/EditorFeatures/Test/CodeAnalysisResources.cs index ab2271a28a82e..210bb03afcff2 100644 --- a/src/EditorFeatures/Test/CodeAnalysisResources.cs +++ b/src/EditorFeatures/Test/CodeAnalysisResources.cs @@ -24,10 +24,7 @@ internal static class CodeAnalysisResources private static string GetString(string resourceName) { - if (s_codeAnalysisResourceManager == null) - { - s_codeAnalysisResourceManager = new ResourceManager(typeof(CodeAnalysisResources).FullName, typeof(Compilation).Assembly); - } + s_codeAnalysisResourceManager ??= new ResourceManager(typeof(CodeAnalysisResources).FullName, typeof(Compilation).Assembly); return s_codeAnalysisResourceManager.GetString(resourceName); } diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs index e7706f9195b6b..529e64816b069 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs @@ -220,6 +220,59 @@ await TestAddNamedTypeAsync(input, expected, modifiers: new Editing.DeclarationModifiers(isStatic: true)); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] + public async Task AddStaticAbstractClass() + { + var input = "namespace [|N|] { }"; + var expected = @"namespace N +{ + public static class C + { + } +}"; + // note that 'abstract' is dropped here + await TestAddNamedTypeAsync(input, expected, + modifiers: new Editing.DeclarationModifiers(isStatic: true, isAbstract: true)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] + [InlineData(Accessibility.NotApplicable)] + [InlineData(Accessibility.Internal)] + [InlineData(Accessibility.Public)] + public async Task AddFileClass(Accessibility accessibility) + { + var input = "namespace [|N|] { }"; + var expected = @"namespace N +{ + file class C + { + } +}"; + // note: when invalid combinations of modifiers+accessibility are present here, + // we actually drop the accessibility. This is similar to what is done if someone declares a 'static abstract class C { }'. + await TestAddNamedTypeAsync(input, expected, + accessibility: accessibility, + modifiers: new Editing.DeclarationModifiers(isFile: true)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] + [InlineData("struct", TypeKind.Struct)] + [InlineData("interface", TypeKind.Interface)] + [InlineData("enum", TypeKind.Enum)] + public async Task AddFileType(string kindString, TypeKind typeKind) + { + var input = "namespace [|N|] { }"; + var expected = @"namespace N +{ + file " + kindString + @" C + { + } +}"; + await TestAddNamedTypeAsync(input, expected, + typeKind: typeKind, + modifiers: new Editing.DeclarationModifiers(isFile: true)); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] public async Task AddSealedClass() { diff --git a/src/EditorFeatures/Test/CommentSelection/CommentUncommentSelectionCommandHandlerTests.cs b/src/EditorFeatures/Test/CommentSelection/CommentUncommentSelectionCommandHandlerTests.cs index 33d869f821cc5..3e148ff21c10e 100644 --- a/src/EditorFeatures/Test/CommentSelection/CommentUncommentSelectionCommandHandlerTests.cs +++ b/src/EditorFeatures/Test/CommentSelection/CommentUncommentSelectionCommandHandlerTests.cs @@ -760,12 +760,12 @@ private static void CommentOrUncommentSelection( { var textUndoHistoryRegistry = exportProvider.GetExportedValue(); var editorOperationsFactory = exportProvider.GetExportedValue(); - var globalOptions = exportProvider.GetExportedValue(); - var commandHandler = new CommentUncommentSelectionCommandHandler(textUndoHistoryRegistry, editorOperationsFactory, globalOptions); + var editorOptionsService = exportProvider.GetExportedValue(); + var commandHandler = new CommentUncommentSelectionCommandHandler(textUndoHistoryRegistry, editorOperationsFactory, editorOptionsService); var service = new MockCommentSelectionService(supportBlockComments); - var edits = commandHandler.CollectEditsAsync( - null, service, textView.TextBuffer, textView.Selection.GetSnapshotSpansOnBuffer(textView.TextBuffer), operation, CancellationToken.None).GetAwaiter().GetResult(); + var edits = commandHandler.CollectEdits( + null, service, textView.TextBuffer, textView.Selection.GetSnapshotSpansOnBuffer(textView.TextBuffer), operation, CancellationToken.None); AssertEx.SetEqual(expectedChanges, edits.TextChanges); diff --git a/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs b/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs index dca97ffde089e..eb31f1c993ed4 100644 --- a/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs @@ -126,6 +126,47 @@ ManagedActiveStatementDebugInfo CreateInfo(int startLine, int startColumn, int e }, oldSpans.Select(s => $"{s.UnmappedSpan} -> {s.Statement.Span} #{s.Statement.Ordinal}")); } + [Fact] + public async Task InvalidActiveStatements() + { + using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); + + var source = @" +class C +{ + void F() + { +S1(); + } +}"; + + var solution = workspace.CurrentSolution + .AddProject("proj", "proj", LanguageNames.CSharp) + .AddDocument("doc", SourceText.From(source, Encoding.UTF8), filePath: "a.cs").Project.Solution; + + var project = solution.Projects.Single(); + var document = project.Documents.Single(); + var analyzer = project.LanguageServices.GetRequiredService(); + + var documentPathMap = new Dictionary>(); + + var moduleId = Guid.NewGuid(); + var token = 0x06000001; + ManagedActiveStatementDebugInfo CreateInfo(int startLine, int startColumn, int endLine, int endColumn, string fileName) + => new(new(new(moduleId, token++, version: 1), ilOffset: 0), fileName, new SourceSpan(startLine, startColumn, endLine, endColumn), ActiveStatementFlags.MethodUpToDate); + + // Create a bad active span that is outside the document, but passes the `TryGetTextSpan` check in ActiveStatementMap + var debugInfos = ImmutableArray.Create( + CreateInfo(7, 9, 7, 10, "a.cs") + ); + + var map = ActiveStatementsMap.Create(debugInfos, remapping: ImmutableDictionary>.Empty); + + var oldSpans = await map.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None); + + AssertEx.Empty(oldSpans); + } + [Fact] public void ExpandMultiLineSpan() { diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index b0495237767d4..dd1d772f2174c 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -1736,15 +1736,23 @@ public async Task HasChanges() { using var _ = CreateWorkspace(out var solution, out var service); + var pathA = Path.Combine(TempRoot.Root, "A.cs"); + var pathB = Path.Combine(TempRoot.Root, "B.cs"); + var pathC = Path.Combine(TempRoot.Root, "C.cs"); + var pathD = Path.Combine(TempRoot.Root, "D.cs"); + var pathX = Path.Combine(TempRoot.Root, "X"); + var pathY = Path.Combine(TempRoot.Root, "Y"); + var pathCommon = Path.Combine(TempRoot.Root, "Common.cs"); + solution = solution. AddProject("A", "A", "C#"). - AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: "A.cs").Project.Solution. + AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: pathA).Project.Solution. AddProject("B", "B", "C#"). - AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. - AddDocument("B.cs", "class B {}", filePath: "B.cs").Project.Solution. + AddDocument("Common.cs", "class Common {}", filePath: pathCommon).Project. + AddDocument("B.cs", "class B {}", filePath: pathB).Project.Solution. AddProject("C", "C", "C#"). - AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. - AddDocument("C.cs", "class C {}", filePath: "C.cs").Project.Solution; + AddDocument("Common.cs", "class Common {}", filePath: pathCommon).Project. + AddDocument("C.cs", "class C {}", filePath: pathC).Project.Solution; var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(debuggingSession); @@ -1757,9 +1765,9 @@ public async Task HasChanges() Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); - Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "Common.cs", CancellationToken.None)); - Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "B.cs", CancellationToken.None)); - Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "C.cs", CancellationToken.None)); + Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathCommon, CancellationToken.None)); + Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathB, CancellationToken.None)); + Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathC, CancellationToken.None)); Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "NonexistentFile.cs", CancellationToken.None)); // All projects must have no errors. @@ -1771,10 +1779,10 @@ public async Task HasChanges() oldSolution = solution; projectC = solution.GetProjectsByName("C").Single(); var documentDId = DocumentId.CreateNewId(projectC.Id); - solution = solution.AddDocument(documentDId, "D", "class D {}", filePath: "D.cs"); + solution = solution.AddDocument(documentDId, "D", "class D {}", filePath: pathD); Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); - Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "D.cs", CancellationToken.None)); + Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathD, CancellationToken.None)); // remove a document: @@ -1782,14 +1790,14 @@ public async Task HasChanges() solution = solution.RemoveDocument(documentDId); Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); - Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "D.cs", CancellationToken.None)); + Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathD, CancellationToken.None)); // add an additional document: oldSolution = solution; projectC = solution.GetProjectsByName("C").Single(); var documentXId = DocumentId.CreateNewId(projectC.Id); - solution = solution.AddAdditionalDocument(documentXId, "X", "xxx", filePath: "X"); + solution = solution.AddAdditionalDocument(documentXId, "X", "xxx", filePath: pathX); Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); @@ -1801,7 +1809,7 @@ public async Task HasChanges() oldSolution = solution; projectC = solution.GetProjectsByName("C").Single(); var documentYId = DocumentId.CreateNewId(projectC.Id); - solution = solution.AddAnalyzerConfigDocument(documentYId, "Y", SourceText.From("yyy"), filePath: "Y"); + solution = solution.AddAnalyzerConfigDocument(documentYId, "Y", SourceText.From("yyy"), filePath: pathY); Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); @@ -3081,7 +3089,10 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() (solution, var documentA) = AddDefaultTestProject(solution, source1); var projectA = documentA.Project; - var projectB = solution.AddProject("B", "A", "C#").AddMetadataReferences(projectA.MetadataReferences).AddDocument("DocB", source1, filePath: "DocB.cs").Project; + var projectB = solution.AddProject("B", "A", "C#"). + AddMetadataReferences(projectA.MetadataReferences). + AddDocument("DocB", source1, filePath: Path.Combine(TempRoot.Root, "DocB.cs")).Project; + solution = projectB.Solution; _mockCompilationOutputsProvider = project => @@ -3448,7 +3459,7 @@ public async Task ActiveStatements_ForeignDocument(bool withPath, bool designTim using var _ = CreateWorkspace(out var solution, out var service, new[] { typeof(DummyLanguageService) }); var project = solution.AddProject("dummy_proj", "dummy_proj", designTimeOnly ? LanguageNames.CSharp : DummyLanguageService.LanguageName); - var filePath = withPath ? Path.Combine(TempRoot.Root, "test") : null; + var filePath = withPath ? Path.Combine(TempRoot.Root, "test.cs") : null; var documentInfo = DocumentInfo.Create( DocumentId.CreateNewId(project.Id, "test"), diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs new file mode 100644 index 0000000000000..6ef8a702134b6 --- /dev/null +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -0,0 +1,520 @@ +// 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.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Snippets +{ + [UseExportProvider] + public class RoslynLSPSnippetConvertTests + { + #region Edgecase extend TextChange tests + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwardsForCaret() + { + var markup = +@"[|if ({|placeholder:true|}) +{ +}|] $$"; + + var expectedLSPSnippet = +@"if (${1:true}) +{ +} $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForCaret() + { + var markup = +@"$$ [|if ({|placeholder:true|}) +{ +}|]"; + + var expectedLSPSnippet = +@"$0 if (${1:true}) +{ +}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwardsForPlaceholder() + { + var markup = +@"[|if (true) +{$$ +}|] {|placeholder:test|}"; + + var expectedLSPSnippet = +@"if (true) +{$0 +} ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholder() + { + var markup = +@"{|placeholder:test|} [|if (true) +{$$ +}|]"; + + var expectedLSPSnippet = +@"${1:test} if (true) +{$0 +}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwardsForPlaceholderThenCaret() + { + var markup = +@"[|if (true) +{ +}|] {|placeholder:test|} $$"; + + var expectedLSPSnippet = +@"if (true) +{ +} ${1:test} $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwardsForCaretThenPlaceholder() + { + var markup = +@"[|if (true) +{ +}|] $$ {|placeholder:test|}"; + + var expectedLSPSnippet = +@"if (true) +{ +} $0 ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholderThenCaret() + { + var markup = +@"{|placeholder:test|} $$ [|if (true) +{ +}|]"; + + var expectedLSPSnippet = +@"${1:test} $0 if (true) +{ +}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForCaretThenPlaceholder() + { + var markup = +@"$$ {|placeholder:test|} [|if (true) +{ +}|]"; + + var expectedLSPSnippet = +@"$0 ${1:test} if (true) +{ +}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForCaretForwardsForPlaceholder() + { + var markup = +@"$$ [|if (true) +{ +}|] {|placeholder:test|}"; + + var expectedLSPSnippet = +@"$0 if (true) +{ +} ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret() + { + var markup = +@"{|placeholder:test|} [|if (true) +{ +}|] $$"; + + var expectedLSPSnippet = +@"${1:test} if (true) +{ +} $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForCaret() + { + var markup = +@"public void Method() +{ + [|if ({|placeholder:true|}) + { + }|] $$ +}"; + + var expectedLSPSnippet = +@"if (${1:true}) + { + } $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForCaret() + { + var markup = +@"public void Method() +{ + $$ [|if ({|placeholder:true|}) + { + }|] +}"; + + var expectedLSPSnippet = +@"$0 if (${1:true}) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholder() + { + var markup = +@"public void Method() +{ + [|if (true) + {$$ + }|] {|placeholder:test|} +}"; + + var expectedLSPSnippet = +@"if (true) + {$0 + } ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholder() + { + var markup = +@"public void Method() +{ + {|placeholder:test|} [|if (true) + {$$ + }|]"; + + var expectedLSPSnippet = +@"${1:test} if (true) + {$0 + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholderThenCaret() + { + var markup = +@"public void Method() +{ + [|if (true) + { + }|] {|placeholder:test|} $$ +}"; + + var expectedLSPSnippet = +@"if (true) + { + } ${1:test} $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForCaretThenPlaceholder() + { + var markup = +@"public void Method() +{ + [|if (true) + { + }|] $$ {|placeholder:test|} +}"; + + var expectedLSPSnippet = +@"if (true) + { + } $0 ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderThenCaret() + { + var markup = +@"public void Method() +{ + {|placeholder:test|} $$ [|if (true) + { + }|] +}"; + + var expectedLSPSnippet = +@"${1:test} $0 if (true) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretThenPlaceholder() + { + var markup = +@"public void Method() +{ + $$ {|placeholder:test|} [|if (true) + { + }|] +}"; + + var expectedLSPSnippet = +@"$0 ${1:test} if (true) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretForwardsForPlaceholder() + { + var markup = +@"public void Method() +{ + $$ [|if (true) + { + }|] {|placeholder:test|} +}"; + + var expectedLSPSnippet = +@"$0 if (true) + { + } ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderForwardsForCaret() + { + var markup = +@"public void Method() +{ + {|placeholder:test|} [|if (true) + { + }|] $$ +}"; + + var expectedLSPSnippet = +@"${1:test} if (true) + { + } $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodWithCodeBeforeAndAfterBackwardsForPlaceholderForwardsForCaret() + { + var markup = +@"public void Method() +{ + var x = 5; + {|placeholder:test|} [|if (true) + { + }|] $$ + + x = 3; +}"; + + var expectedLSPSnippet = +@"${1:test} if (true) + { + } $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public void TestExtendTextChangeInsertion() + { + var testString = "foo bar quux baz"; + using var workspace = CreateWorkspaceFromCode(testString); + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); + var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, caretPosition: 12, + ImmutableArray.Empty, new TextChange(new TextSpan(8, 0), "quux"), triggerLocation: 12, CancellationToken.None).Result; + AssertEx.EqualOrDiff("quux$0", lspSnippetString); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public void TestExtendTextChangeReplacement() + { + var testString = "foo bar quux baz"; + using var workspace = CreateWorkspaceFromCode(testString); + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); + var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, caretPosition: 12, + ImmutableArray.Empty, new TextChange(new TextSpan(4, 4), "bar quux"), triggerLocation: 12, CancellationToken.None).Result; + AssertEx.EqualOrDiff("bar quux$0", lspSnippetString); + } + + #endregion + + #region LSP Snippet generation tests + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestForLoopSnippet() + { + var markup = +@"[|for (var {|placeholder1:i|} = 0; {|placeholder1:i|} < {|placeholder2:length|}; {|placeholder1:i|}++) +{$$ +}|]"; + + var expectedLSPSnippet = +@"for (var ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) +{$0 +}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestIfSnippetSamePlaceholderCursorLocation() + { + var markup = +@"public void Method() +{ + var x = 5; + [|if ({|placeholder:true|}$$) + { + }|] + + x = 3; +}"; + + var expectedLSPSnippet = +@"if (${1:true}$0) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestIfSnippetSameCursorPlaceholderLocation() + { + var markup = +@"public void Method() +{ + var x = 5; + [|if ($${|placeholder:true|}) + { + }|] + + x = 3; +}"; + + var expectedLSPSnippet = +@"if ($0${1:true}) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + #endregion + + protected static TestWorkspace CreateWorkspaceFromCode(string code) + => TestWorkspace.CreateCSharp(code); + + private static async Task TestAsync(string markup, string output) + { + MarkupTestFile.GetPositionAndSpans(markup, out var text, out var cursorPosition, out IDictionary> placeholderDictionary); + var stringSpan = placeholderDictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), text.Substring(stringSpan.Start, stringSpan.Length)); + var placeholders = GetSnippetPlaceholders(text, placeholderDictionary); + using var workspace = CreateWorkspaceFromCode(markup); + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); + + var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange, stringSpan.Start, CancellationToken.None).ConfigureAwait(false); + AssertEx.EqualOrDiff(output, lspSnippetString); + } + + private static ImmutableArray GetSnippetPlaceholders(string text, IDictionary> placeholderDictionary) + { + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + foreach (var kvp in placeholderDictionary) + { + if (kvp.Key.Length > 0) + { + var spans = kvp.Value; + var identifier = text.Substring(spans[0].Start, spans[0].Length); + var placeholders = spans.Select(span => span.Start).ToImmutableArray(); + arrayBuilder.Add(new SnippetPlaceholder(identifier, placeholders)); + } + } + + return arrayBuilder.ToImmutable(); + } + } +} diff --git a/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs b/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs index 546398a55763a..f811766668621 100644 --- a/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs +++ b/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs @@ -1768,11 +1768,8 @@ private static IMethodSymbol GetInvokedSymbol(Compilation compilati var method_root = method.DeclaringSyntaxReferences[0].GetSyntax(); var invocation = method_root.DescendantNodes().OfType().FirstOrDefault(); - if (invocation == null) - { - // vb method root is statement, but we need block to find body with invocation - invocation = method_root.Parent.DescendantNodes().OfType().First(); - } + // vb method root is statement, but we need block to find body with invocation + invocation ??= method_root.Parent.DescendantNodes().OfType().First(); var model = compilation.GetSemanticModel(invocation.SyntaxTree); var info = model.GetSymbolInfo(invocation); diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index b3ca8a311e484..189654c326a60 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -29,6 +29,136 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense <[UseExportProvider]> Public Class CSharpCompletionCommandHandlerTests + + + Public Async Function CompletionOnFileType_SameFile_NonQualified(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + +namespace NS +{ + file class FC { } + + class C + { + public static void M() + { + var x = new $$ + } + } +} + , + showCompletionInArgumentLists:=showCompletionInArgumentLists, languageVersion:=LanguageVersion.Preview) + + state.SendTypeChars("F") + Await state.AssertSelectedCompletionItem(displayText:="FC", isHardSelected:=True) + + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Contains("var x = new FC", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + End Using + End Function + + + + Public Async Function CompletionOnFileType_SameFile_NamespaceQualified(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + +namespace NS +{ + file class FC { } + + class C + { + public static void M() + { + var x = new NS.$$ + } + } +} + , + showCompletionInArgumentLists:=showCompletionInArgumentLists, languageVersion:=LanguageVersion.Preview) + + state.SendTypeChars("F") + Await state.AssertSelectedCompletionItem(displayText:="FC", isHardSelected:=True) + + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Contains("var x = new NS.FC", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + End Using + End Function + + + + Public Async Function CompletionOnFileType_DifferentFile_NonQualified(showCompletionInArgumentLists As Boolean) As Task + Using State = New TestState( + > + +namespace NS +{ + file class FC { } +} + + +namespace NS +{ + class C + { + public static void M() + { + var x = new $$ + } + } +} + + + , + excludedTypes:=Nothing, extraExportedTypes:=Nothing, + includeFormatCommandHandler:=False, workspaceKind:=Nothing) + + State.Workspace.GlobalOptions.SetGlobalOption( + New OptionKey(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp), showCompletionInArgumentLists) + + State.SendTypeChars("F") + Await State.AssertCompletionItemsDoNotContainAny("FC") + End Using + End Function + + + + Public Async Function CompletionOnFileType_DifferentFile_NamespaceQualified(showCompletionInArgumentLists As Boolean) As Task + Using State = New TestState( + > + +namespace NS +{ + file class FC { } +} + + +namespace NS +{ + class C + { + public static void M() + { + var x = new NS.$$ + } + } +} + + + , + excludedTypes:=Nothing, extraExportedTypes:=Nothing, + includeFormatCommandHandler:=False, workspaceKind:=Nothing) + + State.Workspace.GlobalOptions.SetGlobalOption( + New OptionKey(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp), showCompletionInArgumentLists) + + State.SendTypeChars("F") + Await State.AssertCompletionItemsDoNotContainAny("FC") + End Using + End Function + Public Async Function CompletionOnExtendedPropertyPattern_FirstNested(showCompletionInArgumentLists As Boolean) As Task diff --git a/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb b/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb index 11ee202a02739..4a7b69ee0ec57 100644 --- a/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Interactive Imports Microsoft.VisualStudio.InteractiveWindow +Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Editor.Commanding.Commands Imports Microsoft.VisualStudio.Text.Operations @@ -110,6 +111,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) + textView.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) + editorOperations.InsertText("line1") editorOperations.InsertNewLine() editorOperations.InsertText("line2") @@ -148,6 +151,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) + textView.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) editorOperations.InsertText("line1") editorOperations.InsertNewLine() @@ -192,6 +196,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) + textView.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) editorOperations.InsertNewLine() editorOperations.InsertText("line1") diff --git a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb index 89b402dda2697..32ac8faa7e561 100644 --- a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb @@ -143,6 +143,9 @@ End Class , host) Dim view = workspace.Documents.Single().GetTextView() + + view.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) + view.Caret.MoveTo(New SnapshotPoint(view.TextBuffer.CurrentSnapshot, workspace.Documents.Single(Function(d) d.CursorPosition.HasValue).CursorPosition.Value)) Dim commandHandler = CreateCommandHandler(workspace) diff --git a/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs b/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs index 26854a7e55c09..7a38ab30d4245 100644 --- a/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs +++ b/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs @@ -124,6 +124,8 @@ public AbstractCommandHandlerTestState( this.EditorOperations = GetService().GetEditorOperations(_textView); this.UndoHistoryRegistry = GetService(); + + _textView.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart); } public void Dispose() diff --git a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticBraceCompletionTests.cs b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticBraceCompletionTests.cs index 4daaf3a9f50d1..e72c4a51f9fc3 100644 --- a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticBraceCompletionTests.cs +++ b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticBraceCompletionTests.cs @@ -9,12 +9,14 @@ using System.Linq; using Microsoft.CodeAnalysis.AutomaticCompletion; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.BraceCompletion; +using Microsoft.VisualStudio.Text.Editor; using Roslyn.Test.Utilities; using Xunit; @@ -147,24 +149,19 @@ internal static void Type(IBraceCompletionSession session, string text) } } - internal static Holder CreateSession(TestWorkspace workspace, char opening, char closing, Dictionary changedOptionSet = null) + internal static Holder CreateSession(TestWorkspace workspace, char opening, char closing, OptionsCollection globalOptions = null) { - if (changedOptionSet != null) - { - var options = workspace.Options; - foreach (var entry in changedOptionSet) - { - options = options.WithChangedOption(entry.Key, entry.Value); - } - - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(options)); - } - var document = workspace.Documents.First(); - var provider = Assert.IsType(workspace.ExportProvider.GetExportedValue()); + var provider = Assert.IsType(workspace.GetService()); + var openingPoint = new SnapshotPoint(document.GetTextBuffer().CurrentSnapshot, document.CursorPosition.Value); - if (provider.TryCreateSession(document.GetTextView(), openingPoint, opening, closing, out var session)) + var textView = document.GetTextView(); + + globalOptions?.SetGlobalOptions(workspace.GlobalOptions); + workspace.GlobalOptions.SetEditorOptions(textView.Options.GlobalOptions, document.Project.Language); + + if (provider.TryCreateSession(textView, openingPoint, opening, closing, out var session)) { return new Holder(workspace, session); } diff --git a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs index 9462df03b59d8..4c4932e87b6b0 100644 --- a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs +++ b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs @@ -75,12 +75,13 @@ private void Test(string expected, string code, int position, bool useTabs, bool // WPF is required for some reason: https://github.com/dotnet/roslyn/issues/46286 using var workspace = TestWorkspace.Create(Language, compilationOptions: null, parseOptions: null, new[] { markupCode }, composition: EditorTestCompositions.EditorFeaturesWpf); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(FormattingOptions2.UseTabs, Language), useTabs); - var view = workspace.Documents.Single().GetTextView(); var buffer = workspace.Documents.Single().GetTextBuffer(); var nextHandlerInvoked = false; + view.Options.GlobalOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); + view.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart); + view.Caret.MoveTo(new SnapshotPoint(buffer.CurrentSnapshot, workspace.Documents.Single(d => d.CursorPosition.HasValue).CursorPosition.Value)); var commandHandler = GetCommandHandler(workspace); diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index 2c0b863a785a6..0f85893c27c54 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -53,6 +53,7 @@ public abstract class AbstractCompletionProviderTests : TestB protected bool? ForceExpandedCompletionIndexCreation { get; set; } protected bool? HideAdvancedMembers { get; set; } protected bool? ShowNameSuggestions { get; set; } + protected bool? ShowNewSnippetExperience { get; set; } protected AbstractCompletionProviderTests() { @@ -84,6 +85,9 @@ private CompletionOptions GetCompletionOptions() if (ShowNameSuggestions.HasValue) options = options with { ShowNameSuggestions = ShowNameSuggestions.Value }; + if (ShowNewSnippetExperience.HasValue) + options = options with { ShowNewSnippetExperience = ShowNewSnippetExperience.Value }; + return options; } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/GenerateTypeTestState.cs b/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/GenerateTypeTestState.cs index a04329925ee8f..f03ec69ff8913 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/GenerateTypeTestState.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/GenerateTypeTestState.cs @@ -80,10 +80,7 @@ public TestProjectManagementService TestProjectManagementService public void Dispose() { - if (Workspace != null) - { - Workspace.Dispose(); - } + Workspace?.Dispose(); } } } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs index 6d71e27b24d19..27cbe5f358880 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/GenerateType/TestGenerateTypeOptionsService.cs @@ -52,10 +52,7 @@ public GenerateTypeOptionsResult GetGenerateTypeOptions( // Storing the actual values ClassName = className; GenerateTypeDialogOptions = generateTypeDialogOptions; - if (DefaultNamespace == null) - { - DefaultNamespace = projectManagementService.GetDefaultNamespace(Project, Project?.Solution.Workspace); - } + DefaultNamespace ??= projectManagementService.GetDefaultNamespace(Project, Project?.Solution.Workspace); return new GenerateTypeOptionsResult( accessibility: Accessibility, diff --git a/src/EditorFeatures/TestUtilities/DocumentationComments/AbstractDocumentationCommentTests.cs b/src/EditorFeatures/TestUtilities/DocumentationComments/AbstractDocumentationCommentTests.cs index 91865cfae34c4..f7d9404e748e7 100644 --- a/src/EditorFeatures/TestUtilities/DocumentationComments/AbstractDocumentationCommentTests.cs +++ b/src/EditorFeatures/TestUtilities/DocumentationComments/AbstractDocumentationCommentTests.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using Microsoft.CodeAnalysis.DocumentationComments; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; @@ -31,9 +32,9 @@ public abstract class AbstractDocumentationCommentTests protected abstract TestWorkspace CreateTestWorkspace(string code); - protected void VerifyTypingCharacter(string initialMarkup, string expectedMarkup, bool useTabs = false, bool autoGenerateXmlDocComments = true, string newLine = "\r\n") + internal void VerifyTypingCharacter(string initialMarkup, string expectedMarkup, bool useTabs = false, string newLine = "\r\n", bool trimTrailingWhiteSpace = false, OptionsCollection globalOptions = null) { - Verify(initialMarkup, expectedMarkup, useTabs, autoGenerateXmlDocComments, newLine: newLine, + Verify(initialMarkup, expectedMarkup, execute: (workspace, view, editorOperationsFactoryService) => { var commandHandler = CreateCommandHandler(workspace); @@ -42,14 +43,13 @@ protected void VerifyTypingCharacter(string initialMarkup, string expectedMarkup var nextHandler = CreateInsertTextHandler(view, DocumentationCommentCharacter.ToString()); commandHandler.ExecuteCommand(commandArgs, nextHandler, TestCommandExecutionContext.Create()); - }); + }, + useTabs, newLine, trimTrailingWhiteSpace, globalOptions); } - protected void VerifyPressingEnter(string initialMarkup, string expectedMarkup, bool useTabs = false, bool autoGenerateXmlDocComments = true, - Action setOptionsOpt = null) + internal void VerifyPressingEnter(string initialMarkup, string expectedMarkup, bool useTabs = false, string newLine = "\r\n", bool trimTrailingWhiteSpace = false, OptionsCollection globalOptions = null) { - Verify(initialMarkup, expectedMarkup, useTabs, autoGenerateXmlDocComments, - setOptionsOpt: setOptionsOpt, + Verify(initialMarkup, expectedMarkup, execute: (workspace, view, editorOperationsFactoryService) => { var commandHandler = CreateCommandHandler(workspace); @@ -57,12 +57,13 @@ protected void VerifyPressingEnter(string initialMarkup, string expectedMarkup, var commandArgs = new ReturnKeyCommandArgs(view, view.TextBuffer); var nextHandler = CreateInsertTextHandler(view, "\r\n"); commandHandler.ExecuteCommand(commandArgs, nextHandler, TestCommandExecutionContext.Create()); - }); + }, + useTabs, newLine, trimTrailingWhiteSpace, globalOptions); } - protected void VerifyInsertCommentCommand(string initialMarkup, string expectedMarkup, bool useTabs = false, bool autoGenerateXmlDocComments = true) + internal void VerifyInsertCommentCommand(string initialMarkup, string expectedMarkup, bool useTabs = false, string newLine = "\r\n", bool trimTrailingWhiteSpace = false, OptionsCollection globalOptions = null) { - Verify(initialMarkup, expectedMarkup, useTabs, autoGenerateXmlDocComments, + Verify(initialMarkup, expectedMarkup, execute: (workspace, view, editorOperationsFactoryService) => { var commandHandler = CreateCommandHandler(workspace); @@ -71,12 +72,13 @@ protected void VerifyInsertCommentCommand(string initialMarkup, string expectedM Action nextHandler = delegate { }; commandHandler.ExecuteCommand(commandArgs, nextHandler, TestCommandExecutionContext.Create()); - }); + }, + useTabs, newLine, trimTrailingWhiteSpace, globalOptions); } - protected void VerifyOpenLineAbove(string initialMarkup, string expectedMarkup, bool useTabs = false, bool autoGenerateXmlDocComments = true) + internal void VerifyOpenLineAbove(string initialMarkup, string expectedMarkup, bool useTabs = false, string newLine = "\r\n", bool trimTrailingWhiteSpace = false, OptionsCollection globalOptions = null) { - Verify(initialMarkup, expectedMarkup, useTabs, autoGenerateXmlDocComments, + Verify(initialMarkup, expectedMarkup, execute: (workspace, view, editorOperationsFactoryService) => { var commandHandler = CreateCommandHandler(workspace); @@ -89,12 +91,13 @@ void nextHandler() } commandHandler.ExecuteCommand(commandArgs, nextHandler, TestCommandExecutionContext.Create()); - }); + }, + useTabs, newLine, trimTrailingWhiteSpace, globalOptions); } - protected void VerifyOpenLineBelow(string initialMarkup, string expectedMarkup, bool useTabs = false, bool autoGenerateXmlDocComments = true) + internal void VerifyOpenLineBelow(string initialMarkup, string expectedMarkup, bool useTabs = false, string newLine = "\r\n", bool trimTrailingWhiteSpace = false, OptionsCollection globalOptions = null) { - Verify(initialMarkup, expectedMarkup, useTabs, autoGenerateXmlDocComments, + Verify(initialMarkup, expectedMarkup, execute: (workspace, view, editorOperationsFactoryService) => { var commandHandler = CreateCommandHandler(workspace); @@ -107,7 +110,8 @@ void nextHandler() } commandHandler.ExecuteCommand(commandArgs, nextHandler, TestCommandExecutionContext.Create()); - }); + }, + useTabs, newLine, trimTrailingWhiteSpace, globalOptions); } private static Action CreateInsertTextHandler(ITextView textView, string text) @@ -120,35 +124,36 @@ private static Action CreateInsertTextHandler(ITextView textView, string text) }; } - private void Verify(string initialMarkup, string expectedMarkup, bool useTabs, bool autoGenerateXmlDocComments, + private void Verify( + string initialMarkup, + string expectedMarkup, Action execute, - Action setOptionsOpt = null, string newLine = "\r\n") + bool useTabs, + string newLine, + bool trimTrailingWhiteSpace, + OptionsCollection globalOptions) { using (var workspace = CreateTestWorkspace(initialMarkup)) { var testDocument = workspace.Documents.Single(); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, testDocument.Project.Language), autoGenerateXmlDocComments); - - var options = workspace.Options; - - options = options.WithChangedOption(FormattingOptions.UseTabs, testDocument.Project.Language, useTabs); - options = options.WithChangedOption(FormattingOptions.NewLine, testDocument.Project.Language, newLine); - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(options)); - - setOptionsOpt?.Invoke(workspace); Assert.True(testDocument.CursorPosition.HasValue, "No caret position set!"); var startCaretPosition = testDocument.CursorPosition.Value; var view = testDocument.GetTextView(); + globalOptions?.SetGlobalOptions(workspace.GlobalOptions); + + var optionsFactory = workspace.GetService(); + var editorOptions = optionsFactory.GetOptions(view.TextBuffer); + editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); + editorOptions.SetOptionValue(DefaultOptions.NewLineCharacterOptionId, newLine); + view.Options.SetOptionValue(DefaultOptions.TrimTrailingWhiteSpaceOptionId, trimTrailingWhiteSpace); + if (testDocument.SelectedSpans.Any()) { var selectedSpan = testDocument.SelectedSpans[0]; - - var isReversed = selectedSpan.Start == startCaretPosition - ? true - : false; + var isReversed = selectedSpan.Start == startCaretPosition; view.Selection.Select(new SnapshotSpan(view.TextSnapshot, selectedSpan.Start, selectedSpan.Length), isReversed); } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 6a968c778d006..34e399d39809f 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Differencing; @@ -44,6 +45,7 @@ internal abstract class EditAndContinueTestHelpers public abstract ImmutableArray GetDeclarators(ISymbol method); public abstract string LanguageName { get; } + public abstract string ProjectFileExtension { get; } public abstract TreeComparer TopSyntaxComparer { get; } private void VerifyDocumentActiveStatementsAndExceptionRegions( @@ -373,16 +375,19 @@ private static void VerifySyntaxMap( private void CreateProjects(EditScript[] editScripts, AdhocWorkspace workspace, TargetFramework targetFramework, out Project oldProject, out Project newProject) { - oldProject = workspace.AddProject("project", LanguageName).WithMetadataReferences(TargetFrameworkUtil.GetReferences(targetFramework)); - var documentIndex = 0; + var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), name: "project", assemblyName: "project", LanguageName, filePath: Path.Combine(TempRoot.Root, "project" + ProjectFileExtension)); + + oldProject = workspace.AddProject(projectInfo).WithMetadataReferences(TargetFrameworkUtil.GetReferences(targetFramework)); foreach (var editScript in editScripts) { - oldProject = oldProject.AddDocument(documentIndex.ToString(), editScript.Match.OldRoot).Project; - documentIndex++; + var oldRoot = editScript.Match.OldRoot; + var oldPath = oldRoot.SyntaxTree.FilePath; + var name = Path.GetFileNameWithoutExtension(oldPath); + oldProject = oldProject.AddDocument(name, oldRoot, filePath: oldPath).Project; } var newSolution = oldProject.Solution; - documentIndex = 0; + var documentIndex = 0; foreach (var oldDocument in oldProject.Documents) { newSolution = newSolution.WithDocumentSyntaxRoot(oldDocument.Id, editScripts[documentIndex].Match.NewRoot, PreservationMode.PreserveIdentity); diff --git a/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs b/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs index 9a2a3a01af6ba..c358ccc4c8226 100644 --- a/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs +++ b/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs @@ -130,10 +130,7 @@ public async Task ExtractViaCodeAction() public void Dispose() { - if (Workspace != null) - { - Workspace.Dispose(); - } + Workspace?.Dispose(); } } } diff --git a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs index 81a541ff6fed8..c90df388686f6 100644 --- a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs +++ b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs @@ -51,7 +51,7 @@ protected CoreFormatterTestsBase(ITestOutputHelper output) protected abstract SyntaxNode ParseCompilationUnit(string expected); internal static void TestIndentation( - int point, int? expectedIndentation, ITextView textView, TestHostDocument subjectDocument, IGlobalOptionService globalOptions) + int point, int? expectedIndentation, ITextView textView, TestHostDocument subjectDocument, EditorOptionsService editorOptionsService) { var textUndoHistory = new Mock(); var editorOperationsFactory = new Mock(); @@ -61,7 +61,7 @@ internal static void TestIndentation( var snapshot = subjectDocument.GetTextBuffer().CurrentSnapshot; var indentationLineFromBuffer = snapshot.GetLineFromPosition(point); - var provider = new SmartIndent(textView, globalOptions); + var provider = new SmartIndent(textView, editorOptionsService); var actualIndentation = provider.GetDesiredIndentation(indentationLineFromBuffer); Assert.Equal(expectedIndentation, actualIndentation.Value); @@ -75,12 +75,19 @@ internal void TestIndentation( bool useTabs) { var language = GetLanguageName(); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, language), indentStyle); - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options - .WithChangedOption(FormattingOptions2.UseTabs, language, useTabs))); + var editorOptionsFactory = workspace.GetService(); + var document = workspace.Documents.First(); + var textBuffer = document.GetTextBuffer(); + var editorOptions = editorOptionsFactory.GetOptions(textBuffer); - var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot; + editorOptions.SetOptionValue(DefaultOptions.IndentStyleId, indentStyle.ToEditorIndentStyle()); + editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); + + // Remove once https://github.com/dotnet/roslyn/issues/62204 is fixed: + workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, document.Project.Language), indentStyle); + + var snapshot = textBuffer.CurrentSnapshot; var bufferGraph = new Mock(MockBehavior.Strict); bufferGraph.Setup(x => x.MapUpToSnapshot(It.IsAny(), It.IsAny(), @@ -106,7 +113,9 @@ internal void TestIndentation( textView.Setup(x => x.BufferGraph).Returns(bufferGraph.Object); textView.SetupGet(x => x.TextSnapshot.TextBuffer).Returns(projectionBuffer.Object); - var provider = new SmartIndent(textView.Object, workspace.GlobalOptions); + var provider = new SmartIndent( + textView.Object, + workspace.GetService()); var indentationLineFromBuffer = snapshot.GetLineFromLineNumber(indentationLine); var actualIndentation = provider.GetDesiredIndentation(indentationLineFromBuffer); diff --git a/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs b/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs index 66e407b32b492..730f4d9d73366 100644 --- a/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs +++ b/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.CommandHandlers; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -37,12 +39,15 @@ protected async Task AssertNavigatedAsync(string code, bool next, SourceCodeKind { var hostDocument = workspace.DocumentWithCursor; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); - Assert.Empty((await document.GetSyntaxTreeAsync()).GetDiagnostics()); - var targetPosition = await GoToAdjacentMemberCommandHandler.GetTargetPositionAsync( - document, + var parsedDocument = await ParsedDocument.CreateAsync(document, CancellationToken.None); + Assert.Empty(parsedDocument.SyntaxTree.GetDiagnostics()); + var service = document.GetRequiredLanguageService(); + + var targetPosition = GoToAdjacentMemberCommandHandler.GetTargetPosition( + service, + parsedDocument.Root, hostDocument.CursorPosition.Value, - next, - CancellationToken.None); + next); Assert.NotNull(targetPosition); Assert.Equal(hostDocument.SelectedSpans.Single().Start, targetPosition.Value); @@ -60,13 +65,14 @@ protected async Task AssertNavigatedAsync(string code, bool next, SourceCodeKind { var hostDocument = workspace.DocumentWithCursor; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); - Assert.Empty((await document.GetSyntaxTreeAsync()).GetDiagnostics()); + var parsedDocument = await ParsedDocument.CreateAsync(document, CancellationToken.None); + Assert.Empty(parsedDocument.SyntaxTree.GetDiagnostics()); - return await GoToAdjacentMemberCommandHandler.GetTargetPositionAsync( - document, + return GoToAdjacentMemberCommandHandler.GetTargetPosition( + document.GetRequiredLanguageService(), + parsedDocument.Root, hostDocument.CursorPosition.Value, - next, - CancellationToken.None); + next); } } } diff --git a/src/EditorFeatures/TestUtilities/MoveStaticMembers/TestMoveStaticMembersService.cs b/src/EditorFeatures/TestUtilities/MoveStaticMembers/TestMoveStaticMembersService.cs index bc02f360ee914..ce7c381fafdf3 100644 --- a/src/EditorFeatures/TestUtilities/MoveStaticMembers/TestMoveStaticMembersService.cs +++ b/src/EditorFeatures/TestUtilities/MoveStaticMembers/TestMoveStaticMembersService.cs @@ -2,10 +2,14 @@ // 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.Linq; +using System.Threading; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.MoveStaticMembers; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Test.Utilities.MoveStaticMembers { @@ -20,23 +24,46 @@ public TestMoveStaticMembersService() { } - public string? DestinationType { get; set; } + public string? DestinationName { get; set; } public ImmutableArray SelectedMembers { get; set; } + public ImmutableArray ExpectedPrecheckedMembers { get; set; } + public string? Filename { get; set; } - public MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ISymbol? selectedNodeSymbol) + public bool CreateNew { get; set; } = true; + + public MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ImmutableArray selectedNodeSymbols) { + if (!ExpectedPrecheckedMembers.IsEmpty) + { + // if we expect to have prechecked members and don't have the correct ones, error + var actualPrecheckedMembers = selectedNodeSymbols.SelectAsArray(n => n.Name).Sort(); + if (!ExpectedPrecheckedMembers.Sort().SequenceEqual(actualPrecheckedMembers)) + { + System.Diagnostics.Debug.Fail("Expected Prechecked members did not match recieved members"); + var errMsg = string.Format("Expected: {0} \n Actual: {1}", ExpectedPrecheckedMembers, actualPrecheckedMembers); + System.Diagnostics.Debug.Fail(errMsg); + throw new InvalidOperationException(errMsg); + } + } + var selectedMembers = selectedType.GetMembers().WhereAsArray(symbol => SelectedMembers.Contains(symbol.Name)); - var namespaceDisplay = selectedType.ContainingNamespace.IsGlobalNamespace - ? string.Empty - : selectedType.ContainingNamespace.ToDisplayString(); - // just return all the selected members - return new MoveStaticMembersOptions( - Filename!, - string.Join(".", namespaceDisplay, DestinationType!), - selectedMembers); + if (CreateNew) + { + var namespaceDisplay = selectedType.ContainingNamespace.IsGlobalNamespace + ? string.Empty + : selectedType.ContainingNamespace.ToDisplayString(); + // just return all the selected members + return new MoveStaticMembersOptions( + Filename!, + string.Join(".", namespaceDisplay, DestinationName!), + selectedMembers); + } + + var destination = selectedType.ContainingNamespace.GetAllTypes(CancellationToken.None).First(t => t.ToDisplayString() == DestinationName); + return new MoveStaticMembersOptions(destination, selectedMembers); } } } diff --git a/src/EditorFeatures/TestUtilities/PullMemberUp/TestPullMemberUpService.cs b/src/EditorFeatures/TestUtilities/PullMemberUp/TestPullMemberUpService.cs index e1c2742a638e7..1202cac72ec42 100644 --- a/src/EditorFeatures/TestUtilities/PullMemberUp/TestPullMemberUpService.cs +++ b/src/EditorFeatures/TestUtilities/PullMemberUp/TestPullMemberUpService.cs @@ -26,16 +26,17 @@ public TestPullMemberUpService(IEnumerable<(string member, bool makeAbstract)> s DestinationName = destinationName; } - public PullMembersUpOptions GetPullMemberUpOptions(Document document, ISymbol selectedNodeSymbol) + public PullMembersUpOptions GetPullMemberUpOptions(Document document, ImmutableArray selectedNodeSymbols) { - var members = selectedNodeSymbol.ContainingType.GetMembers().Where(member => MemberAndDestinationValidator.IsMemberValid(member)); + var containingType = selectedNodeSymbols[0].ContainingType; + var members = containingType.GetMembers().Where(member => MemberAndDestinationValidator.IsMemberValid(member)); var selectedMember = _selectedMembers == null ? members.Select(member => (member, false)) : _selectedMembers.Select(selection => (members.Single(symbol => symbol.Name == selection.member), selection.makeAbstract)); - var allInterfaces = selectedNodeSymbol.ContainingType.AllInterfaces; - var baseClass = selectedNodeSymbol.ContainingType.BaseType; + var allInterfaces = containingType.AllInterfaces; + var baseClass = containingType.BaseType; INamedTypeSymbol destination = null; if (DestinationName == null) @@ -44,7 +45,7 @@ public PullMembersUpOptions GetPullMemberUpOptions(Document document, ISymbol se if (destination == null) { - throw new ArgumentException($"No target base type for {selectedNodeSymbol}"); + throw new ArgumentException($"No target base type for {containingType}"); } } else diff --git a/src/EditorFeatures/TestUtilities/Utilities/GlobalOptionsExtensions.cs b/src/EditorFeatures/TestUtilities/Utilities/GlobalOptionsExtensions.cs new file mode 100644 index 0000000000000..e0d2f9959d08b --- /dev/null +++ b/src/EditorFeatures/TestUtilities/Utilities/GlobalOptionsExtensions.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.Test.Utilities; + +internal static class GlobalOptionsExtensions +{ + /// + /// Sets options stored in that are read by command handlers from the text editor to given . + /// + public static void SetEditorOptions(this IGlobalOptionService globalOptions, IEditorOptions editorOptions, string language) + { + editorOptions.SetOptionValue(DefaultOptions.IndentStyleId, globalOptions.GetOption(FormattingOptions2.SmartIndent, language).ToEditorIndentStyle()); + editorOptions.SetOptionValue(DefaultOptions.NewLineCharacterOptionId, globalOptions.GetOption(FormattingOptions2.NewLine, language)); + editorOptions.SetOptionValue(DefaultOptions.TabSizeOptionId, globalOptions.GetOption(FormattingOptions2.TabSize, language)); + editorOptions.SetOptionValue(DefaultOptions.IndentSizeOptionId, globalOptions.GetOption(FormattingOptions2.IndentationSize, language)); + editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !globalOptions.GetOption(FormattingOptions2.UseTabs, language)); + } +} diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs index ff1b09aab8c88..5ac6703cd3872 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs @@ -195,10 +195,7 @@ internal void SetProject(TestHostProject project) } } - if (_languageServiceProvider == null) - { - _languageServiceProvider = project.LanguageServiceProvider; - } + _languageServiceProvider ??= project.LanguageServiceProvider; } private class TestDocumentLoader : TextLoader diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 4af7bd190824a..373b02b4edf44 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -125,18 +125,12 @@ public TestHostDocument DocumentWithCursor protected override void OnDocumentTextChanged(Document document) { - if (_backgroundParser != null) - { - _backgroundParser.Parse(document); - } + _backgroundParser?.Parse(document); } protected override void OnDocumentClosing(DocumentId documentId) { - if (_backgroundParser != null) - { - _backgroundParser.CancelParse(documentId); - } + _backgroundParser?.CancelParse(documentId); } public new void RegisterText(SourceTextContainer text) @@ -168,10 +162,7 @@ protected override void Dispose(bool finalize) document.CloseTextView(); } - if (_backgroundParser != null) - { - _backgroundParser.CancelAllParses(); - } + _backgroundParser?.CancelAllParses(); base.Dispose(finalize); } diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_Create.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_Create.cs index 3602ea8926fb1..1c94ed978e938 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_Create.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_Create.cs @@ -179,7 +179,7 @@ internal static TestWorkspace Create( extension = language; } - documentElements.Add(CreateDocumentElement(files[i], GetDefaultTestSourceDocumentName(index++, extension), parseOptions == null ? null : parseOptions[i])); + documentElements.Add(CreateDocumentElement(files[i], GetDefaultTestSourceDocumentName(index++, extension), parseOptions?[i])); } var workspaceElement = CreateWorkspaceElement( diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb index 4310a552a29ef..cc5fe75ac8526 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb @@ -63,6 +63,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense ' Disable editor's responsive completion option to ensure a deterministic test behavior MyBase.TextView.Options.GlobalOptions.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, False) + MyBase.TextView.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) Dim languageServices = Me.Workspace.CurrentSolution.Projects.First().LanguageServices Dim language = languageServices.Language diff --git a/src/EditorFeatures/VisualBasic/AutomaticCompletion/AutomaticLineEnderCommandHandler.vb b/src/EditorFeatures/VisualBasic/AutomaticCompletion/AutomaticLineEnderCommandHandler.vb index 78191f3f7a8ed..5d95701bf9851 100644 --- a/src/EditorFeatures/VisualBasic/AutomaticCompletion/AutomaticLineEnderCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/AutomaticCompletion/AutomaticLineEnderCommandHandler.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Commanding Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion +Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Editor.Commanding.Commands Imports Microsoft.VisualStudio.Text.Operations Imports Microsoft.VisualStudio.Utilities @@ -30,9 +31,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticCompletion Public Sub New(undoRegistry As ITextUndoHistoryRegistry, editorOperations As IEditorOperationsFactoryService, - globalOptions As IGlobalOptionService) + editorOptionsService As EditorOptionsService) - MyBase.New(undoRegistry, editorOperations, globalOptions) + MyBase.New(undoRegistry, editorOperations, editorOptionsService) End Sub Protected Overrides Sub NextAction(editorOperation As IEditorOperations, nextAction As Action) diff --git a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb index 395db41ad26b6..adf1516f82f55 100644 --- a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb @@ -11,6 +11,7 @@ Imports Microsoft.VisualStudio.Text.Operations Imports Microsoft.VisualStudio.Utilities Imports Microsoft.CodeAnalysis.DocumentationComments Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.Text.Editor Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.DocumentationComments @@ -27,9 +28,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.DocumentationComments uiThreadOperationExecutor As IUIThreadOperationExecutor, undoHistoryRegistry As ITextUndoHistoryRegistry, editorOperationsFactoryService As IEditorOperationsFactoryService, - globalOptions As IGlobalOptionService) + editorOptionsService As EditorOptionsService) - MyBase.New(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService, globalOptions) + MyBase.New(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService, editorOptionsService) End Sub Protected Overrides ReadOnly Property ExteriorTriviaText As String diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb index 8571ecac956aa..3f83b7c900cd9 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb @@ -32,16 +32,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit p.Name <> PredefinedCodeCleanupProviderNames.Format End Function - Private ReadOnly _globalOptions As IGlobalOptionService - Private ReadOnly _indentationManager As IIndentationManagerService - Private ReadOnly _editorOptionsFactory As IEditorOptionsFactoryService + Private ReadOnly _editorOptionsService As EditorOptionsService - Public Sub New(indentationManager As IIndentationManagerService, editorOptionsFactory As IEditorOptionsFactoryService, globalOptions As IGlobalOptionService) - _indentationManager = indentationManager - _editorOptionsFactory = editorOptionsFactory - _globalOptions = globalOptions + Public Sub New(editorOptionsService As EditorOptionsService) + _editorOptionsService = editorOptionsService End Sub Public Sub CommitRegion(spanToFormat As SnapshotSpan, @@ -68,7 +64,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Return End If - If Not (isExplicitFormat OrElse _globalOptions.GetOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic)) Then + If Not (isExplicitFormat OrElse _editorOptionsService.GlobalOptions.GetOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic)) Then Return End If @@ -80,8 +76,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End If ' create commit formatting cleanup provider that has line commit specific behavior - Dim fallbackOptions = _globalOptions.GetVisualBasicSyntaxFormattingOptions() - Dim formattingOptions = _indentationManager.GetInferredFormattingOptions(buffer, _editorOptionsFactory, document.Project.LanguageServices, fallbackOptions, isExplicitFormat) + Dim formattingOptions = buffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.LanguageServices, isExplicitFormat) Dim commitFormattingCleanup = GetCommitFormattingCleanupProvider( document.Id, document.Project.LanguageServices, @@ -98,7 +93,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Concat(commitFormattingCleanup) Dim cleanupService = document.GetRequiredLanguageService(Of ICodeCleanerService) - Dim cleanupOptions = document.GetCodeCleanupOptionsAsync(_globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken) + Dim cleanupOptions = document.GetCodeCleanupOptionsAsync(_editorOptionsService.GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken) Dim finalDocument As Document If useSemantics OrElse isExplicitFormat Then diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb index 81b5dded0aee7..cb19dac8e86e6 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb @@ -7,18 +7,10 @@ Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeRefactorings Imports Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeRefactoringVerifier(Of Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable.VisualBasicIntroduceParameterCodeRefactoringProvider) + Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings.IntroduceParameter Public Class IntroduceParameterTests - Inherits AbstractVisualBasicCodeActionTest - - Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider - Return New VisualBasicIntroduceParameterCodeRefactoringProvider() - End Function - - Protected Overrides Function MassageActions(actions As ImmutableArray(Of CodeAction)) As ImmutableArray(Of CodeAction) - Return GetNestedActions(actions) - End Function - Public Async Function TestExpressionWithNoMethodCallsCase() As Task Dim source = @@ -32,7 +24,12 @@ End Class" Sub M(x As Integer, y As Integer, z As Integer, num As Integer) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -48,7 +45,7 @@ End Class" M(z, y, x) End Sub End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -72,7 +69,12 @@ End Class" M(y, 5, 2, y.Length * 5 * 2) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -96,7 +98,12 @@ End Class" M(z, y, x, z * y * x) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -104,14 +111,14 @@ End Class" Dim source = "Class Program Sub M(x As Integer, y As Integer, z As Integer) - Dim num = [|x * y * z|], y = 0 + Dim num = [|x * {|BC32000:y|} * z|], {|BC30734:y|} = 0 End Sub Sub M1(x As Integer, y As Integer, z As Integer) M(z, y, x) End Sub End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -126,7 +133,7 @@ End Class" M(z, y, x) End Sub End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -139,7 +146,7 @@ End Class" Sub M1(x As Integer, y As Integer, z As Integer) M(z, y, x) - M(a + b, 5, x) + M({|BC30451:a|} + {|BC30451:b|}, 5, x) End Sub End Class" Dim expected = @@ -149,10 +156,15 @@ End Class" Sub M1(x As Integer, y As Integer, z As Integer) M(z, y, x, z * y * x) - M(a + b, 5, x, (a + b) * 5 * x) + M({|BC30451:a|} + {|BC30451:b|}, 5, x, ({|BC30451:a|} + {|BC30451:b|}) * 5 * x) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -178,7 +190,12 @@ End Class" M(z, y, x, z * y * x) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=3) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 3 + }.RunAsync() End Function @@ -198,7 +215,12 @@ End Class" Sub M(x As Integer, y As Integer, z As Integer, num As Integer) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -226,7 +248,12 @@ End Class" M(z, y, x, GetNum(z, y, x)) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -257,7 +284,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=4) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 4 + }.RunAsync() End Function @@ -285,7 +317,12 @@ End Class" Me.M(z, y, x, GetNum(z, y, x)) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -313,7 +350,12 @@ End Class" Me?.M(z, y, x, Me?.GetNum(z, y, x)) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -357,7 +399,12 @@ Class B Return age End Function End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -401,7 +448,12 @@ Class B Return age End Function End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -445,7 +497,12 @@ Class B Return age End Function End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -489,7 +546,12 @@ Class B Return age End Function End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -518,7 +580,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=2) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 2 + }.RunAsync() End Function @@ -537,7 +604,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -556,7 +628,12 @@ End Class" End Function End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -569,7 +646,7 @@ End Class" End Function End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -594,7 +671,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -619,7 +701,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -650,7 +737,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=2) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 2 + }.RunAsync() End Function @@ -681,7 +773,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -712,7 +809,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -739,7 +841,12 @@ Class Program End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -764,7 +871,12 @@ End Class" End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -781,7 +893,7 @@ End Class" End Sub End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -799,7 +911,7 @@ End Class" End Property End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -817,7 +929,7 @@ End Class" End Property End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -828,7 +940,7 @@ End Class" Dim prod = [|1 * 5|] End Sub End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -839,7 +951,7 @@ End Class" End Sub End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -863,7 +975,12 @@ End Class" Public Sub M(x As Integer, y As Integer) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -876,7 +993,7 @@ End Class" End Function Sub M1() - M(z:=0, y:=2) + {|BC30455:M|}(z:=0, y:=2) End Sub End Class" Dim expected = @@ -886,11 +1003,16 @@ End Class" End Function Sub M1() - M(z:=0, num:=2 * 0, y:=2) + {|BC30455:M|}(z:=0, num:=2 * 0, y:=2) End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=0) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 0 + }.RunAsync() End Function @@ -929,7 +1051,12 @@ Class A End Sub End Class" - Await TestInRegularAndScriptAsync(source, expected, index:=1) + Await New VerifyVB.Test With + { + .TestCode = source, + .FixedCode = expected, + .CodeActionIndex = 1 + }.RunAsync() End Function @@ -941,7 +1068,7 @@ End Class" End Function End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function @@ -963,7 +1090,7 @@ Class TestClass End Function End Class" - Await TestMissingInRegularAndScriptAsync(source) + Await VerifyVB.VerifyRefactoringAsync(source, source) End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicCommentSelectionTests.vb b/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicCommentSelectionTests.vb index 4f57d4ddce968..c718218239dc4 100644 --- a/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicCommentSelectionTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicCommentSelectionTests.vb @@ -7,6 +7,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CommentSelection Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Editor @@ -85,7 +86,7 @@ End Module Dim commandHandler = New CommentUncommentSelectionCommandHandler( workspace.GetService(Of ITextUndoHistoryRegistry), workspace.GetService(Of IEditorOperationsFactoryService), - workspace.GlobalOptions) + workspace.GetService(Of EditorOptionsService)) Dim textView = doc.GetTextView() Dim textBuffer = doc.GetTextBuffer() commandHandler.ExecuteCommand(textView, textBuffer, operation, TestCommandExecutionContext.Create()) diff --git a/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb b/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb index 889b5aaf8cf75..a0b86ea565627 100644 --- a/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb +++ b/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb @@ -2,7 +2,9 @@ ' 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.DocumentationComments Imports Microsoft.CodeAnalysis.Editor.UnitTests +Imports Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions Imports Microsoft.CodeAnalysis.Editor.UnitTests.DocumentationComments Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces @@ -28,7 +30,10 @@ End Class Class C End Class " - VerifyTypingCharacter(code, expected, autoGenerateXmlDocComments:=False) + VerifyTypingCharacter(code, expected, globalOptions:=New OptionsCollection(LanguageNames.VisualBasic) From + { + {DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, False} + }) End Sub @@ -280,7 +285,10 @@ $$ Class C End Class " - VerifyPressingEnter(code, expected, autoGenerateXmlDocComments:=False) + VerifyPressingEnter(code, expected, globalOptions:=New OptionsCollection(LanguageNames.VisualBasic) From + { + {DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, False} + }) End Sub @@ -548,7 +556,10 @@ End Class Class C End Class " - VerifyPressingEnter(code, expected, autoGenerateXmlDocComments:=False) + VerifyPressingEnter(code, expected, globalOptions:=New OptionsCollection(LanguageNames.VisualBasic) From + { + {DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, False} + }) End Sub @@ -866,7 +877,10 @@ Class C End Class " - VerifyInsertCommentCommand(code, expected, autoGenerateXmlDocComments:=False) + VerifyInsertCommentCommand(code, expected, globalOptions:=New OptionsCollection(LanguageNames.VisualBasic) From + { + {DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, False} + }) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index 968c52d21ce66..8df47921fb8a3 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.IO Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.Contracts @@ -102,11 +103,15 @@ End Namespace Return New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, lineEdits:=Nothing, diagnostics) End Function + Private Shared Function GetDocumentFilePath(documentIndex As Integer) As String + Return Path.Combine(TempRoot.Root, documentIndex.ToString() & ".vb") + End Function + Private Shared Function ParseSource(markedSource As String, Optional documentIndex As Integer = 0) As SyntaxTree Return SyntaxFactory.ParseSyntaxTree( ActiveStatementsDescription.ClearTags(markedSource), VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest), - path:=documentIndex.ToString()) + path:=GetDocumentFilePath(documentIndex)) End Function Friend Shared Function GetTopEdits(src1 As String, src2 As String, Optional documentIndex As Integer = 0) As EditScript(Of SyntaxNode) @@ -190,8 +195,8 @@ End Namespace End Select End Function - Friend Shared Function GetActiveStatements(oldSource As String, newSource As String, Optional flags As ActiveStatementFlags() = Nothing, Optional path As String = "0") As ActiveStatementsDescription - Return New ActiveStatementsDescription(oldSource, newSource, Function(source) SyntaxFactory.ParseSyntaxTree(source, path:=path), flags) + Friend Shared Function GetActiveStatements(oldSource As String, newSource As String, Optional flags As ActiveStatementFlags() = Nothing, Optional documentIndex As Integer = 0) As ActiveStatementsDescription + Return New ActiveStatementsDescription(oldSource, newSource, Function(source) SyntaxFactory.ParseSyntaxTree(source, path:=GetDocumentFilePath(documentIndex)), flags) End Function Friend Shared Function GetSyntaxMap(oldSource As String, newSource As String) As SyntaxMapDescription diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb index 2c42b8ad76e5c..ca8a2c157ab5e 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb @@ -32,6 +32,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue End Get End Property + Public Overrides ReadOnly Property ProjectFileExtension As String + Get + Return ".vbproj" + End Get + End Property + Public Overrides ReadOnly Property TopSyntaxComparer As TreeComparer(Of SyntaxNode) Get Return SyntaxComparer.TopLevel diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 0535d0dc291ba..c7f64898e9f6f 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -3,6 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.IO +Imports System.Text Imports System.Threading Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue @@ -21,6 +23,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests EditorTestCompositions.EditorFeatures #Region "Helpers" + Private Shared Function CreateWorkspace() As TestWorkspace + Return New TestWorkspace(composition:=s_composition) + End Function + + Private Shared Function AddDefaultTestProject(solution As Solution, source As String) As Solution + + Dim pid = ProjectId.CreateNewId() + + Return solution. + AddProject(ProjectInfo.Create(pid, VersionStamp.Create(), "proj", "proj", LanguageNames.VisualBasic)).GetProject(pid). + AddDocument("test.vb", SourceText.From(source, Encoding.UTF8), filePath:=Path.Combine(TempRoot.Root, "test.vb")).Project.Solution + End Function + Private Shared Sub TestSpans(source As String, hasLabel As Func(Of SyntaxNode, Boolean)) Dim tree = SyntaxFactory.ParseSyntaxTree(ClearSource(source)) @@ -452,14 +467,13 @@ Class C End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source1, composition:=s_composition) - - Dim oldSolution = workspace.CurrentSolution + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.First() Dim oldDocument = oldProject.Documents.Single() Dim documentId = oldDocument.Id - Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) + Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim oldText = Await oldDocument.GetTextAsync() Dim oldSyntaxRoot = Await oldDocument.GetSyntaxRootAsync() Dim newDocument = newSolution.GetDocument(documentId) @@ -509,8 +523,9 @@ Class C End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source, composition:=s_composition) - Dim oldProject = workspace.CurrentSolution.Projects.Single() + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source) + Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim result = Await AnalyzeDocumentAsync(oldProject, oldDocument) @@ -538,12 +553,12 @@ Class C End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source1, composition:=s_composition) - Dim oldProject = workspace.CurrentSolution.Projects.Single() + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) + Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim documentId = oldDocument.Id - Dim oldSolution = workspace.CurrentSolution - Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) + Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim result = Await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)) @@ -564,8 +579,9 @@ Class C End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source, composition:=s_composition) - Dim oldProject = workspace.CurrentSolution.Projects.Single() + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source) + Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim result = Await AnalyzeDocumentAsync(oldProject, oldDocument) @@ -595,12 +611,12 @@ Class C End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source1, composition:=s_composition) - Dim oldProject = workspace.CurrentSolution.Projects.Single() + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) + Dim oldProject = oldSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() Dim documentId = oldDocument.Id - Dim oldSolution = workspace.CurrentSolution - Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) + Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim result = Await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)) @@ -627,11 +643,11 @@ Class C End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source1, composition:=s_composition) - Dim oldSolution = workspace.CurrentSolution + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.Single() Dim documentId = oldProject.Documents.Single().Id - Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) + Dim newSolution = oldSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim result = Await AnalyzeDocumentAsync(oldProject, newSolution.GetDocument(documentId)) Assert.True(result.HasChanges) @@ -658,13 +674,11 @@ Class D End Class " - Using workspace = TestWorkspace.CreateVisualBasic(source1, composition:=s_composition) - Dim oldSolution = workspace.CurrentSolution + Using workspace = CreateWorkspace() + Dim oldSolution = AddDefaultTestProject(workspace.CurrentSolution, source1) Dim oldProject = oldSolution.Projects.Single() Dim newDocId = DocumentId.CreateNewId(oldProject.Id) - Dim newSolution = oldSolution.AddDocument(newDocId, "goo.vb", SourceText.From(source2)) - - workspace.TryApplyChanges(newSolution) + Dim newSolution = oldSolution.AddDocument(newDocId, "goo.vb", SourceText.From(source2), filePath:=Path.Combine(TempRoot.Root, "goo.vb")) Dim newProject = newSolution.Projects.Single() Dim changes = newProject.GetChanges(oldProject) diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb index 0f144ae5cd0e8..3bbfef19e3760 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb @@ -213,6 +213,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EndConstructGenera globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) Dim view = workspace.Documents.First().GetTextView() + view.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) + Dim line = view.TextSnapshot.GetLineFromLineNumber(beforeCaret(0)) If beforeCaret(1) = -1 Then view.Caret.MoveTo(line.End) diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb index 39e3bc2e9bfee..5722c67857e52 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb @@ -7,7 +7,9 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules +Imports Microsoft.CodeAnalysis.Options Imports Microsoft.VisualStudio.Text +Imports Microsoft.VisualStudio.Text.Editor Imports Xunit.Abstractions Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting.Indentation @@ -3004,10 +3006,19 @@ end class" factory.TextSpan = subjectDocument.SelectedSpans.Single() Dim indentationLine = projectedDocument.GetTextBuffer().CurrentSnapshot.GetLineFromPosition(projectedDocument.CursorPosition.Value) - Dim point = projectedDocument.GetTextView().BufferGraph.MapDownToBuffer(indentationLine.Start, PointTrackingMode.Negative, subjectDocument.GetTextBuffer(), PositionAffinity.Predecessor) + Dim textBuffer = subjectDocument.GetTextBuffer() + Dim point = projectedDocument.GetTextView().BufferGraph.MapDownToBuffer(indentationLine.Start, PointTrackingMode.Negative, textBuffer, PositionAffinity.Predecessor) + + Dim editorOptionsService = workspace.GetService(Of EditorOptionsService) + Dim editorOptions = editorOptionsService.Factory.GetOptions(textBuffer) + editorOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) TestIndentation( - point.Value, expectedIndentation, projectedDocument.GetTextView(), subjectDocument, workspace.GlobalOptions) + point.Value, + expectedIndentation, + projectedDocument.GetTextView(), + subjectDocument, + editorOptionsService) End Using End Sub diff --git a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb index 9f9c29da79e5d..2098a98ebde0c 100644 --- a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb +++ b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb @@ -38,6 +38,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.LineCommit Public Sub New(workspace As TestWorkspace) Me.Workspace = workspace View = workspace.Documents.Single().GetTextView() + View.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) + EditorOperations = workspace.GetService(Of IEditorOperationsFactoryService).GetEditorOperations(View) Dim position = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue).CursorPosition.Value diff --git a/src/EditorFeatures/VisualBasicTest/MoveStaticMembers/VisualBasicMoveStaticMembersTests.vb b/src/EditorFeatures/VisualBasicTest/MoveStaticMembers/VisualBasicMoveStaticMembersTests.vb index fd4c3d85b4b84..68333d2f52198 100644 --- a/src/EditorFeatures/VisualBasicTest/MoveStaticMembers/VisualBasicMoveStaticMembersTests.vb +++ b/src/EditorFeatures/VisualBasicTest/MoveStaticMembers/VisualBasicMoveStaticMembersTests.vb @@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.MoveStaticMembers Private Shared ReadOnly s_testServices As TestComposition = FeaturesTestCompositions.Features.AddParts(GetType(TestMoveStaticMembersService)) -#Region "Perform Actions From Options" +#Region "Perform New Type Action From Options" Public Async Function TestMoveField() As Task Dim initialMarkup = " @@ -41,6 +41,35 @@ End Namespace Await TestMovementNewFileAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) End Function + + + Public Async Function TestMoveField_MultipleDeclarators() As Task + Dim initialMarkup = " +Class Program + + Public Shared G[||]oo As Integer, Bar As Integer + +End Class +" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Goo") + Dim expectedText1 = " +Class Program + + Public Shared Bar As Integer + +End Class +" + Dim expectedText2 = "Class Class1Helpers + + Public Shared Goo As Integer +End Class +" + + Await TestMovementNewFileAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + Public Async Function TestMoveProperty() As Task Dim initialMarkup = " @@ -1936,6 +1965,445 @@ End Namespace End Function #End Region +#Region "Perform Existing Type Action From Options" + + Public Async Function TestMoveFieldToExistingType() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared Test[||]Field As Integer = 0 +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestField") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Shared TestField As Integer = 0 +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMovePropertyToExistingType() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared ReadOnly Property Test[||]Prop As Integer + Get + Return 0 + End Get + End Property +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestProp") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Shared ReadOnly Property TestProp As Integer + Get + Return 0 + End Get + End Property +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveEventToExistingType() As Task + Dim initialSourceMarkup = " +Imports System + +Public Class Class1 + Public Shared Event Test[||]Event As EventHandler +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestEvent") + Dim fixedSourceMarkup = " +Imports System + +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = "Imports System + +Public Class Class1Helpers + Public Shared Event TestEvent As EventHandler +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveFunctionToExistingType() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared Function Test[||]Func() As Integer + Return 0 + End Function +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Shared Function TestFunc() As Integer + Return 0 + End Function +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveSubToExistingType() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared Sub Test[||]Sub() + Return + End Sub +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestSub") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Shared Sub TestSub() + Return + End Sub +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveConstToExistingType() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Const Test[||]Field As Integer = 0 +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestField") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Const TestField As Integer = 0 +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveExtensionFunctionToExistingType() As Task + Dim initialSourceMarkup = " +Imports System.Runtime.CompilerServices + +Public Module Class1 + + Public Function Test[||]Func(other As Other) As Integer + Return other.OtherInt + 2 + End Function +End Module + +Public Class Class2 + Public Function GetOtherInt() As Integer + Dim other = New Other() + Return other.TestFunc() + End Function +End Class + +Public Class Other + Public OtherInt As Integer + + Public Sub New() + OtherInt = 5 + End Sub +End Class" + Dim initialDestinationMarkup = " +Public Module Class1Helpers +End Module" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Imports System.Runtime.CompilerServices + +Public Module Class1 +End Module + +Public Class Class2 + Public Function GetOtherInt() As Integer + Dim other = New Other() + Return other.TestFunc() + End Function +End Class + +Public Class Other + Public OtherInt As Integer + + Public Sub New() + OtherInt = 5 + End Sub +End Class" + Dim fixedDestinationMarkup = "Imports System.Runtime.CompilerServices + +Public Module Class1Helpers + + Public Function Test[||]Func(other As Other) As Integer + Return other.OtherInt + 2 + End Function +End Module" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveFunctionToExistingTypeWithNamespace() As Task + Dim initialSourceMarkup = " +Namespace TestNs + Public Class Class1 + Public Shared Function Test[||]Func() As Integer + Return 0 + End Function + End Class +End Namespace" + Dim initialDestinationMarkup = " +Namespace TestNs + Public Class Class1Helpers + End Class +End Namespace" + Dim newTypeName = "TestNs.Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Namespace TestNs + Public Class Class1 + End Class +End Namespace" + Dim fixedDestinationMarkup = " +Namespace TestNs + Public Class Class1Helpers + Public Shared Function TestFunc() As Integer + Return 0 + End Function + End Class +End Namespace" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveFunctionToExistingTypeWithNewNamespace() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared Function Test[||]Func() As Integer + Return 0 + End Function +End Class" + Dim initialDestinationMarkup = " +Namespace TestNs + Public Class Class1Helpers + End Class +End Namespace" + Dim newTypeName = "TestNs.Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Namespace TestNs + Public Class Class1Helpers + Public Shared Function TestFunc() As Integer + Return 0 + End Function + End Class +End Namespace" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveFunctionToExistingTypeRefactorSourceUsage() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared Function Test[||]Func() As Integer + Return 0 + End Function + + Public Shared Function TestFunc2() As Integer + Return TestFunc() + End Function +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Public Class Class1 + Public Shared Function TestFunc2() As Integer + Return Class1Helpers.TestFunc() + End Function +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Shared Function TestFunc() As Integer + Return 0 + End Function +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveFunctionToExistingModuleRefactorSourceUsage() As Task + Dim initialSourceMarkup = " +Public Module Class1 + Public Function Test[||]Func() As Integer + Return 0 + End Function + + Public Function TestFunc2() As Integer + Return TestFunc() + End Function +End Module" + Dim initialDestinationMarkup = " +Public Module Class1Helpers +End Module" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Public Module Class1 + Public Function TestFunc2() As Integer + Return TestFunc() + End Function +End Module" + Dim fixedDestinationMarkup = " +Public Module Class1Helpers + Public Function TestFunc() As Integer + Return 0 + End Function +End Module" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function + + + Public Async Function TestMoveFunctionToExistingTypeRefactorDestinationUsage() As Task + Dim initialSourceMarkup = " +Public Class Class1 + Public Shared Function Test[||]Func() As Integer + Return 0 + End Function +End Class" + Dim initialDestinationMarkup = " +Public Class Class1Helpers + Public Shared Function TestFunc2() As Integer + Return Class1.TestFunc() + End Function +End Class" + Dim newTypeName = "Class1Helpers" + Dim selection = ImmutableArray.Create("TestFunc") + Dim fixedSourceMarkup = " +Public Class Class1 +End Class" + Dim fixedDestinationMarkup = " +Public Class Class1Helpers + Public Shared Function TestFunc2() As Integer + Return Class1Helpers.TestFunc() + End Function + Public Shared Function TestFunc() As Integer + Return 0 + End Function +End Class" + + Await TestMovementExistingFileAsync(initialSourceMarkup, + initialDestinationMarkup, + fixedSourceMarkup, + fixedDestinationMarkup, + newTypeName, + selection).ConfigureAwait(False) + End Function +#End Region #Region "SelectionTests" @@ -2042,6 +2510,214 @@ End Namespace Await TestMovementNewFileAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) End Function + ' There seems to be some whitespace formatting errors when we select multiple members in the following tests + ' Mostly, when we "split" a variable, a newline should be added but isn't + + Public Async Function TestSelectMultipleFieldDeclarations() As Task + Dim initialMarkup = " +Namespace TestNs + Public Class Class1 + [|Public Shared Foo As Integer = 0, Goo As Integer = 0|] + End Class +End Namespace" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Foo", "Goo") + Dim expectedText1 = " +Namespace TestNs + Public Class Class1 + End Class +End Namespace" + Dim expectedText2 = "Namespace TestNs + Class Class1Helpers + Public Shared Foo As Integer = 0 + Public Shared Goo As Integer = 0 + End Class +End Namespace +" + + Await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + + + Public Async Function TestSelectOneOfMultipleFieldDeclarations() As Task + Dim initialMarkup = " +Namespace TestNs + Public Class Class1 + Public Shared F[||]oo As Integer = 0, Goo As Integer = 0 + End Class +End Namespace" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Foo") + Dim expectedText1 = " +Namespace TestNs + Public Class Class1 + Public Shared Goo As Integer = 0 + End Class +End Namespace" + Dim expectedText2 = "Namespace TestNs + Class Class1Helpers + Public Shared Foo As Integer = 0 + End Class +End Namespace +" + + Await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + + + Public Async Function TestSelectMultipleMembers1() As Task + Dim initialMarkup = " +Namespace TestNs + Public Class Class1 + [|Public Shared Foo As Integer = 0 + + Public Shared Function DoSomething() As Integer + Return 4 + End Function|] + End Class +End Namespace" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Foo", "DoSomething") + Dim expectedText1 = " +Namespace TestNs + Public Class Class1 + End Class +End Namespace" + Dim expectedText2 = "Namespace TestNs + Class Class1Helpers + Public Shared Foo As Integer = 0 + + Public Shared Function DoSomething() As Integer + Return 4 + End Function + End Class +End Namespace +" + + Await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + + + Public Async Function TestSelectMultipleMembers2() As Task + Dim initialMarkup = " +Namespace TestNs + Public Class Class1 + Public Shared Function DoSomething() As Integer + Return 4 + End [|Function + Public Shared Foo As Integer = 0|] + End Class +End Namespace" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Foo") + Dim expectedText1 = " +Namespace TestNs + Public Class Class1 + Public Shared Function DoSomething() As Integer + Return 4 + End Function + End Class +End Namespace" + Dim expectedText2 = "Namespace TestNs + Class Class1Helpers + Public Shared Foo As Integer = 0 + End Class +End Namespace +" + + Await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + + + Public Async Function TestSelectMultipleMembers3() As Task + Dim initialMarkup = " +Namespace TestNs + Public Class Class1 + Public Shared ReadOnly Property Prop As Integer + Get + Return 4 + [|End Get + End Property + Public Shared Foo As Integer = 0 + Public Shared Function DoSometh|]ing() As Integer + Return 4 + End Function + End Class +End Namespace" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Foo", "DoSomething") + Dim expectedText1 = " +Namespace TestNs + Public Class Class1 + Public Shared ReadOnly Property Prop As Integer + Get + Return 4 + End Get + End Property + End Class +End Namespace" + Dim expectedText2 = "Namespace TestNs + Class Class1Helpers + Public Shared Foo As Integer = 0 + + Public Shared Function DoSomething() As Integer + Return 4 + End Function + End Class +End Namespace +" + + Await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + + + Public Async Function TestSelectMultipleMembers4() As Task + Dim initialMarkup = " +Namespace TestNs + Public Class Class1 + Public Shared ReadOnly Property [|Prop As Integer + Get + Return 4 + End Get + End Property + Public Shared Foo As Integer = 0 + Public Shared F|]unction DoSomething() As Integer + Return 4 + End Function + End Class +End Namespace" + Dim newTypeName = "Class1Helpers" + Dim newFileName = "Class1Helpers.vb" + Dim selection = ImmutableArray.Create("Foo", "Prop") + Dim expectedText1 = " +Namespace TestNs + Public Class Class1 + Public Shared Function DoSomething() As Integer + Return 4 + End Function + End Class +End Namespace" + Dim expectedText2 = "Namespace TestNs + Class Class1Helpers + Public Shared Foo As Integer = 0 + + Public Shared ReadOnly Property Prop As Integer + Get + Return 4 + End Get + End Property + End Class +End Namespace +" + + Await TestMovementNewFileWithSelectionAsync(initialMarkup, expectedText1, expectedText2, newFileName, selection, newTypeName) + End Function + Public Async Function TestSelectInMethodParens() As Task Dim initialMarkup = " @@ -2233,10 +2909,14 @@ End Namespace" Public Sub New(destinationType As String, members As ImmutableArray(Of String), - newFileName As String) + newFileName As String, + Optional testPreselection As Boolean = False, + Optional newType As Boolean = True) _destinationType = destinationType _members = members _newFileName = newFileName + _testPreselection = testPreselection + _newType = newType End Sub Private ReadOnly _destinationType As String @@ -2245,14 +2925,23 @@ End Namespace" Private ReadOnly _newFileName As String + Private ReadOnly _testPreselection As Boolean + + Private ReadOnly _newType As Boolean + Protected Overrides Function CreateWorkspaceImpl() As Workspace Dim hostServices = s_testServices.GetHostServices() - Dim workspace = New AdhocWorkspace(hostServices) Dim optionsService = DirectCast(workspace.Services.GetRequiredService(Of IMoveStaticMembersOptionsService)(), TestMoveStaticMembersService) - optionsService.DestinationType = _destinationType + optionsService.DestinationName = _destinationType optionsService.Filename = _newFileName optionsService.SelectedMembers = _members + If _testPreselection Then + optionsService.ExpectedPrecheckedMembers = _members + Else + optionsService.ExpectedPrecheckedMembers = ImmutableArray(Of String).Empty + End If + optionsService.CreateNew = _newType Return workspace End Function @@ -2271,6 +2960,44 @@ End Namespace" } test.FixedState.Sources.Add(expectedSource) test.FixedState.Sources.Add((newFileName, expectedNewFile)) + Await test.RunAsync().ConfigureAwait(False) + End Function + + Private Shared Async Function TestMovementExistingFileAsync(initialSourceMarkup As String, + initialDestinationMarkup As String, + fixedSourceMarkup As String, + fixedDestinationMarkup As String, + destinationName As String, + selectedMembers As ImmutableArray(Of String), + Optional destinationFileName As String = Nothing) As Task + Dim test = New Test(destinationName, selectedMembers, destinationFileName, newType:=False) + test.TestState.Sources.Add(initialSourceMarkup) + test.FixedState.Sources.Add(fixedSourceMarkup) + + If destinationFileName IsNot Nothing Then + test.TestState.Sources.Add((destinationFileName, initialDestinationMarkup)) + test.FixedState.Sources.Add((destinationFileName, fixedDestinationMarkup)) + Else + test.TestState.Sources.Add(initialDestinationMarkup) + test.FixedState.Sources.Add(fixedDestinationMarkup) + End If + + Await test.RunAsync().ConfigureAwait(False) + End Function + + Private Shared Async Function TestMovementNewFileWithSelectionAsync(initialMarkup As String, + expectedSource As String, + expectedNewFile As String, + newFileName As String, + selectedMembers As ImmutableArray(Of String), + newTypeName As String) As Task + + Dim test = New Test(newTypeName, selectedMembers, newFileName, testPreselection:=True) With + { + .TestCode = initialMarkup + } + test.FixedState.Sources.Add(expectedSource) + test.FixedState.Sources.Add((newFileName, expectedNewFile)) Await test.RunAsync() End Function diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Binders/EEMethodBinder.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Binders/EEMethodBinder.cs index 5b197c99e8a73..c7b5cc398148e 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Binders/EEMethodBinder.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Binders/EEMethodBinder.cs @@ -44,7 +44,8 @@ internal EEMethodBinder(EEMethodSymbol method, MethodSymbol containingMethod, Bi var substitutedSourceMethod = method.SubstitutedSourceMethod; _parameterOffset = substitutedSourceMethod.IsStatic ? 0 : 1; _targetParameters = method.Parameters; - _sourceBinder = new InMethodBinder(substitutedSourceMethod, new BuckStopsHereBinder(next.Compilation)); + // https://github.com/dotnet/roslyn/issues/62334: add tests and adjust implementation to allow EE to access file types + _sourceBinder = new InMethodBinder(substitutedSourceMethod, new BuckStopsHereBinder(next.Compilation, associatedSyntaxTree: null)); } internal override void LookupSymbolsInSingleBinder(LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.cs index faad706ee6fac..d69d5dd1828ad 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.cs @@ -80,10 +80,7 @@ internal static EvaluationContext CreateTypeContext( // Re-use the previous compilation if possible. compilation = previousMetadataContext.Compilation; - if (compilation == null) - { - compilation = metadataBlocks.ToCompilation(moduleVersionId, kind); - } + compilation ??= metadataBlocks.ToCompilation(moduleVersionId, kind); var context = EvaluationContext.CreateTypeContext( compilation, diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs index 7cd2b047799b1..c29de94be92eb 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs @@ -706,7 +706,8 @@ private static Binder CreateBinderChain( @namespace = @namespace.ContainingNamespace; } - Binder binder = new BuckStopsHereBinder(compilation); + // https://github.com/dotnet/roslyn/issues/62334: add tests and adjust implementation to allow EE to access file types + Binder binder = new BuckStopsHereBinder(compilation, associatedSyntaxTree: null); var hasImports = !importRecordGroups.IsDefaultOrEmpty; var numImportStringGroups = hasImports ? importRecordGroups.Length : 0; var currentStringGroup = numImportStringGroups - 1; diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs index bb174945a96de..139f77c35008d 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs @@ -174,13 +174,13 @@ public override bool TryGetPreviousLambda(SyntaxNode lambdaOrLambdaBodySyntax, b return false; } - public override bool TryGetPreviousStateMachineState(SyntaxNode awaitOrYieldSyntax, out int stateOrdinal) + public override bool TryGetPreviousStateMachineState(SyntaxNode awaitOrYieldSyntax, out StateMachineState state) { - stateOrdinal = 0; + state = 0; return false; } - public override int? GetFirstUnusedStateMachineState(bool increasing) => null; + public override StateMachineState? GetFirstUnusedStateMachineState(bool increasing) => null; public override string? PreviousStateMachineTypeName => null; public override int PreviousHoistedLocalSlotCount => 0; public override int PreviousAwaiterSlotCount => 0; diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs index b6856a5f05ff1..2ee0b8d7df52f 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs @@ -159,6 +159,10 @@ internal override bool MangleName get { return false; } } + // https://github.com/dotnet/roslyn/issues/61999 + // Determine if 'null' is the right return value here + internal override SyntaxTree AssociatedSyntaxTree => null; + public override IEnumerable MemberNames { get { throw ExceptionUtilities.Unreachable; } diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs index 643aac2ec98bd..37eceb09df401 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs @@ -426,10 +426,7 @@ private string GetName(string source, string methodName, DkmVariableInfoFlags ar } var name = instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder); - if (builder != null) - { - builder.Free(); - } + builder?.Free(); return name; } @@ -437,7 +434,7 @@ private string GetName(string source, string methodName, DkmVariableInfoFlags ar private string GetReturnTypeName(string source, string methodName, Type[] typeArguments = null) { var instructionDecoder = CSharpInstructionDecoder.Instance; - var serializedTypeArgumentNames = typeArguments?.Select(t => (t != null) ? t.AssemblyQualifiedName : null).ToArray(); + var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray(); var method = GetConstructedMethod(source, methodName, serializedTypeArgumentNames, instructionDecoder); return instructionDecoder.GetReturnTypeName(method); diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.cs index 14f9fd413db72..d44ca8f76d609 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.cs @@ -424,10 +424,7 @@ internal static TResult CompileWithRetry( { if (!missingAssemblyIdentities.IsEmpty) { - if (assembliesLoadedInRetryLoop == null) - { - assembliesLoadedInRetryLoop = PooledHashSet.GetInstance(); - } + assembliesLoadedInRetryLoop ??= PooledHashSet.GetInstance(); // If any identities failed to add (they were already in the list), then don't retry. if (assembliesLoadedInRetryLoop.AddAll(missingAssemblyIdentities)) { diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs index f87096fbaa0d0..ce459dbd3ad23 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs @@ -97,10 +97,7 @@ public static unsafe MethodDebugInfo ReadMethodDebugI try { var symMethod = symReader.GetMethodByVersion(methodToken, methodVersion); - if (symMethod != null) - { - symMethod.GetAllScopes(allScopes, containingScopes, ilOffset, isScopeEndInclusive: isVisualBasicMethod); - } + symMethod?.GetAllScopes(allScopes, containingScopes, ilOffset, isScopeEndInclusive: isVisualBasicMethod); ImmutableArray> importRecordGroups; ImmutableArray externAliasRecords; @@ -665,10 +662,7 @@ private static void GetConstants( } var dynamicFlags = default(ImmutableArray); - if (dynamicLocalConstantMap != null) - { - dynamicLocalConstantMap.TryGetValue(name, out dynamicFlags); - } + dynamicLocalConstantMap?.TryGetValue(name, out dynamicFlags); var tupleElementNames = default(ImmutableArray); if (tupleLocalConstantMap != null) diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/DebuggerTypeProxyExpansion.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/DebuggerTypeProxyExpansion.cs index a93e485f8c5a1..d68eb03cd7a53 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/DebuggerTypeProxyExpansion.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/DebuggerTypeProxyExpansion.cs @@ -177,10 +177,7 @@ internal override void GetRows( bool visitAll, ref int index) { - if (_proxyItem != null) - { - _proxyItem.Expansion.GetRows(resultProvider, rows, inspectionContext, _proxyItem.ToDataItem(), _proxyItem.Value, startIndex, count, visitAll, ref index); - } + _proxyItem?.Expansion.GetRows(resultProvider, rows, inspectionContext, _proxyItem.ToDataItem(), _proxyItem.Value, startIndex, count, visitAll, ref index); if (InRange(startIndex, count, index)) { diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/RootHiddenExpansion.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/RootHiddenExpansion.cs index 01d1ead860784..5c2547750bf6e 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/RootHiddenExpansion.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/RootHiddenExpansion.cs @@ -66,10 +66,7 @@ internal override void GetRows( ExpansionFlags.IncludeBaseMembers | ExpansionFlags.IncludeResultsView, supportsFavorites: false); var expansion = other.Expansion; - if (expansion != null) - { - expansion.GetRows(resultProvider, rows, inspectionContext, other.ToDataItem(), other.Value, startIndex, count, visitAll, ref index); - } + expansion?.GetRows(resultProvider, rows, inspectionContext, other.ToDataItem(), other.Value, startIndex, count, visitAll, ref index); } } } diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/TupleExpansion.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/TupleExpansion.cs index e06c5febff354..cccd4427ea831 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/TupleExpansion.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Expansion/TupleExpansion.cs @@ -260,10 +260,7 @@ internal Fields(ReadOnlyCollection defaultView, bool includeRawView) private Fields GetFields() { - if (_lazyFields == null) - { - _lazyFields = GetFields(_typeAndInfo, _cardinality, _useRawView); - } + _lazyFields ??= GetFields(_typeAndInfo, _cardinality, _useRawView); return _lazyFields; } diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs index 93f1840110eb3..1864e8eee82d6 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs @@ -557,10 +557,7 @@ private static Dictionary GetDebu { continue; } - if (result == null) - { - result = new Dictionary(); - } + result ??= new Dictionary(); // There can be multiple same attributes for derived classes. // Debugger provides attributes starting from derived classes and then up to base ones. diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs index 721c759b4099b..22c3737825536 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs @@ -63,10 +63,7 @@ internal ResultProvider(IDkmClrFormatter2 formatter2, IDkmClrFullNameProvider fu void IDkmClrResultProvider.GetResult(DkmClrValue value, DkmWorkList workList, DkmClrType declaredType, DkmClrCustomTypeInfo declaredTypeInfo, DkmInspectionContext inspectionContext, ReadOnlyCollection formatSpecifiers, string resultName, string resultFullName, DkmCompletionRoutine completionRoutine) { - if (formatSpecifiers == null) - { - formatSpecifiers = Formatter.NoFormatSpecifiers; - } + formatSpecifiers ??= Formatter.NoFormatSpecifiers; if (resultFullName != null) { ReadOnlyCollection otherSpecifiers; @@ -608,12 +605,9 @@ internal EvalResult CreateDataItem( formatSpecifiers, flags, Formatter2.GetEditableValueString(value, inspectionContext, declaredTypeAndInfo.Info)); - if (expansion == null) - { - expansion = value.HasExceptionThrown() + expansion ??= value.HasExceptionThrown() ? this.GetTypeExpansion(inspectionContext, new TypeAndCustomInfo(value.Type), value, expansionFlags, supportsFavorites: false) : this.GetTypeExpansion(inspectionContext, declaredTypeAndInfo, value, expansionFlags, supportsFavorites: supportsFavorites); - } } return new EvalResult( diff --git a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs index d602dcf4c985e..b2cf692ad5453 100644 --- a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs +++ b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs @@ -77,6 +77,10 @@ internal NamespaceTypeDefinitionNoBase(INamespaceTypeDefinition underlyingType) bool INamedTypeReference.MangleName => UnderlyingType.MangleName; +#nullable enable + string? INamedTypeReference.AssociatedFileIdentifier => UnderlyingType.AssociatedFileIdentifier; +#nullable disable + string INamedEntity.Name => UnderlyingType.Name; string INamespaceTypeReference.NamespaceName => UnderlyingType.NamespaceName; diff --git a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/RuntimeInstance.cs b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/RuntimeInstance.cs index adb8a635c3047..83916c46cba62 100644 --- a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/RuntimeInstance.cs +++ b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/RuntimeInstance.cs @@ -46,10 +46,7 @@ internal static RuntimeInstance Create( { var module = compilation.ToModuleInstance(debugFormat, includeLocalSignatures); - if (references == null) - { - references = ExpressionCompilerTestHelpers.GetEmittedReferences(compilation, module.GetMetadataReader()); - } + references ??= ExpressionCompilerTestHelpers.GetEmittedReferences(compilation, module.GetMetadataReader()); if (includeIntrinsicAssembly) { diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrRuntimeInstance.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrRuntimeInstance.cs index dcbaba8b660e7..725443a2f5da2 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrRuntimeInstance.cs +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrRuntimeInstance.cs @@ -47,10 +47,7 @@ internal DkmClrRuntimeInstance( bool enableNativeDebugging = false) : base(enableNativeDebugging) { - if (getModule == null) - { - getModule = (r, a) => new DkmClrModuleInstance(r, a, (a != null) ? new DkmModule(a.GetName().Name + ".dll") : null); - } + getModule ??= (r, a) => new DkmClrModuleInstance(r, a, (a != null) ? new DkmModule(a.GetName().Name + ".dll") : null); this.Assemblies = assemblies; this.Modules = assemblies.Select(a => getModule(this, a)).Where(m => m != null).ToArray(); _defaultModule = getModule(this, null); diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs index c323cd1fb0f88..69fa61e36d87e 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs @@ -229,7 +229,7 @@ public string EvaluateToString(DkmInspectionContext inspectionContext) var rawValue = RawValue; Debug.Assert(rawValue != null || this.Type.GetLmrType().IsVoid(), "In our mock system, this should only happen for void."); - return rawValue == null ? null : rawValue.ToString(); + return rawValue?.ToString(); } /// diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb index aad85d9ce0e75..d44c9b520a0a3 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb @@ -191,12 +191,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Return False End Function - Public Overrides Function TryGetPreviousStateMachineState(awaitOrYieldSyntax As SyntaxNode, ByRef stateOrdinal As Integer) As Boolean - stateOrdinal = 0 + Public Overrides Function TryGetPreviousStateMachineState(awaitOrYieldSyntax As SyntaxNode, ByRef state As StateMachineState) As Boolean + state = 0 Return False End Function - Public Overrides Function GetFirstUnusedStateMachineState(increasing As Boolean) As Integer? + Public Overrides Function GetFirstUnusedStateMachineState(increasing As Boolean) As StateMachineState? Return Nothing End Function diff --git a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs index 587e246fdbbcf..56b48a7ad9027 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs @@ -33,7 +33,7 @@ internal abstract class AbstractCurlyBraceOrBracketCompletionService : AbstractC protected abstract int AdjustFormattingEndPoint(ParsedDocument document, int startPoint, int endPoint); - public sealed override async Task GetTextChangesAfterCompletionAsync(BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken) + public sealed override BraceCompletionResult? GetTextChangesAfterCompletion(BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken) { // After the closing brace is completed we need to format the span from the opening point to the closing point. // E.g. when the user triggers completion for an if statement ($$ is the caret location) we insert braces to get @@ -46,11 +46,9 @@ internal abstract class AbstractCurlyBraceOrBracketCompletionService : AbstractC return null; } - var documentSyntax = await ParsedDocument.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - var (formattingChanges, finalCurlyBraceEnd) = FormatTrackingSpan( - documentSyntax, - context.Document.Project.LanguageServices, + context.Document, + context.Document.LanguageServices, context.OpeningPoint, context.ClosingPoint, // We're not trying to format the indented block here, so no need to pass in additional rules. @@ -64,7 +62,7 @@ internal abstract class AbstractCurlyBraceOrBracketCompletionService : AbstractC } // The caret location should be at the start of the closing brace character. - var formattedText = documentSyntax.Text.WithChanges(formattingChanges); + var formattedText = context.Document.Text.WithChanges(formattingChanges); var caretLocation = formattedText.Lines.GetLinePosition(finalCurlyBraceEnd - 1); return new BraceCompletionResult(formattingChanges, caretLocation); @@ -88,7 +86,7 @@ private static bool ContainsOnlyWhitespace(SourceText text, int openingPosition, return true; } - public sealed override async Task GetTextChangeAfterReturnAsync( + public sealed override BraceCompletionResult? GetTextChangeAfterReturn( BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken) @@ -96,7 +94,7 @@ private static bool ContainsOnlyWhitespace(SourceText text, int openingPosition, var document = context.Document; var closingPoint = context.ClosingPoint; var openingPoint = context.OpeningPoint; - var originalDocumentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var originalDocumentText = document.Text; // check whether shape of the braces are what we support // shape must be either "{|}" or "{ }". | is where caret is. otherwise, we don't do any special behavior @@ -128,12 +126,12 @@ private static bool ContainsOnlyWhitespace(SourceText text, int openingPosition, closingPoint += newLineString.Length; } - var documentSyntax = await ParsedDocument.CreateAsync(document.WithText(textToFormat), cancellationToken).ConfigureAwait(false); + var documentToFormat = document.WithChangedText(textToFormat, cancellationToken); // Format the text that contains the newly inserted line. var (formattingChanges, newClosingPoint) = FormatTrackingSpan( - documentSyntax, - document.Project.LanguageServices, + documentToFormat, + document.LanguageServices, openingPoint, closingPoint, braceFormattingIndentationRules: GetBraceFormattingIndentationRulesAfterReturn(options), @@ -148,9 +146,8 @@ private static bool ContainsOnlyWhitespace(SourceText text, int openingPosition, Debug.Assert(desiredCaretLine.GetFirstNonWhitespacePosition() == null, "the line between the formatted braces is not empty"); // Set the caret position to the properly indented column in the desired line. - var newDocument = document.WithText(formattedText); - var newDocumentText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); - var caretPosition = GetIndentedLinePosition(newDocument, newDocumentText, desiredCaretLine.LineNumber, options, cancellationToken); + var newDocument = document.WithChangedText(formattedText, cancellationToken); + var caretPosition = GetIndentedLinePosition(newDocument, newDocument.Text, desiredCaretLine.LineNumber, options, cancellationToken); // The new line edit is calculated against the original text, d0, to get text d1. // The formatting edits are calculated against d1 to get text d2. @@ -164,9 +161,9 @@ static TextLine GetLineBetweenCurlys(int closingPosition, SourceText text) return text.Lines[closingBraceLineNumber - 1]; } - static LinePosition GetIndentedLinePosition(Document document, SourceText sourceText, int lineNumber, IndentationOptions options, CancellationToken cancellationToken) + static LinePosition GetIndentedLinePosition(ParsedDocument document, SourceText sourceText, int lineNumber, IndentationOptions options, CancellationToken cancellationToken) { - var indentationService = document.GetRequiredLanguageService(); + var indentationService = document.LanguageServices.GetRequiredService(); var indentation = indentationService.GetIndentation(document, lineNumber, options, cancellationToken); var baseLinePosition = sourceText.Lines.GetLinePosition(indentation.BasePosition); diff --git a/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs index 92883d1a3531a..bce21752a0f9f 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs @@ -33,8 +33,8 @@ public BracketBraceCompletionService() protected override char ClosingBrace => Bracket.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken); protected override bool IsValidOpeningBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.OpenBracketToken); diff --git a/src/Features/CSharp/Portable/BraceCompletion/CharLiteralBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/CharLiteralBraceCompletionService.cs index af0ce72935a8a..c44228b2c6b07 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/CharLiteralBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/CharLiteralBraceCompletionService.cs @@ -28,8 +28,8 @@ public CharLiteralBraceCompletionService() protected override char ClosingBrace => SingleQuote.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken) - => AllowOverTypeWithValidClosingTokenAsync(braceCompletionContext, cancellationToken); + public override bool AllowOverType(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken) + => AllowOverTypeWithValidClosingToken(braceCompletionContext); protected override bool IsValidOpeningBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.CharacterLiteralToken); diff --git a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs index 84ef6b5f3ddbc..7a74140a5ba0b 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs @@ -38,18 +38,18 @@ public CurlyBraceCompletionService() protected override char ClosingBrace => CurlyBrace.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken); - public override async Task CanProvideBraceCompletionAsync(char brace, int openingPosition, Document document, CancellationToken cancellationToken) + public override bool CanProvideBraceCompletion(char brace, int openingPosition, ParsedDocument document, CancellationToken cancellationToken) { // Only potentially valid for curly brace completion if not in an interpolation brace completion context. - if (OpeningBrace == brace && await InterpolationBraceCompletionService.IsPositionInInterpolationContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(false)) + if (OpeningBrace == brace && InterpolationBraceCompletionService.IsPositionInInterpolationContext(document, openingPosition)) { return false; } - return await base.CanProvideBraceCompletionAsync(brace, openingPosition, document, cancellationToken).ConfigureAwait(false); + return base.CanProvideBraceCompletion(brace, openingPosition, document, cancellationToken); } protected override bool IsValidOpeningBraceToken(SyntaxToken token) diff --git a/src/Features/CSharp/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.cs index f31579ba3cdf6..ff854a671c08a 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.cs @@ -31,15 +31,15 @@ public InterpolatedStringBraceCompletionService() protected override char OpeningBrace => DoubleQuote.OpenCharacter; protected override char ClosingBrace => DoubleQuote.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeWithValidClosingToken(context); /// /// Only return this service as valid when we're starting an interpolated string. /// Otherwise double quotes should be completed using the /// - public override async Task CanProvideBraceCompletionAsync(char brace, int openingPosition, Document document, CancellationToken cancellationToken) - => OpeningBrace == brace && await IsPositionInInterpolatedStringContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(false); + public override bool CanProvideBraceCompletion(char brace, int openingPosition, ParsedDocument document, CancellationToken cancellationToken) + => OpeningBrace == brace && IsPositionInInterpolatedStringContext(document, openingPosition, cancellationToken); protected override bool IsValidOpeningBraceToken(SyntaxToken leftToken) => leftToken.IsKind(SyntaxKind.InterpolatedStringStartToken) || leftToken.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken); @@ -53,9 +53,9 @@ protected override bool IsValidOpenBraceTokenAtPosition(SourceText text, SyntaxT /// /// Returns true when the input position could be starting an interpolated string if opening quotes were typed. /// - public static async Task IsPositionInInterpolatedStringContextAsync(Document document, int position, CancellationToken cancellationToken) + public static bool IsPositionInInterpolatedStringContext(ParsedDocument document, int position, CancellationToken cancellationToken) { - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var text = document.Text; var start = position - 1; if (start < 0) @@ -73,9 +73,7 @@ public static async Task IsPositionInInterpolatedStringContextAsync(Docume return false; // Verify that we are actually in an location allowed for an interpolated string. - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var token = root.FindToken(start); + var token = document.Root.FindToken(start); if (token.Kind() is not SyntaxKind.InterpolatedStringStartToken and not SyntaxKind.InterpolatedVerbatimStringStartToken and not SyntaxKind.StringLiteralToken and @@ -86,8 +84,8 @@ not SyntaxKind.StringLiteralToken and var previousToken = token.GetPreviousToken(); - return root.SyntaxTree.IsExpressionContext(token.SpanStart, previousToken, attributes: true, cancellationToken) - || root.SyntaxTree.IsStatementContext(token.SpanStart, previousToken, cancellationToken); + return document.SyntaxTree.IsExpressionContext(token.SpanStart, previousToken, attributes: true, cancellationToken) + || document.SyntaxTree.IsStatementContext(token.SpanStart, previousToken, cancellationToken); } } } diff --git a/src/Features/CSharp/Portable/BraceCompletion/InterpolationBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/InterpolationBraceCompletionService.cs index 3a0136c11b129..0490bc8a2d4ad 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/InterpolationBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/InterpolationBraceCompletionService.cs @@ -30,15 +30,15 @@ public InterpolationBraceCompletionService() protected override char OpeningBrace => CurlyBrace.OpenCharacter; protected override char ClosingBrace => CurlyBrace.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeWithValidClosingToken(context); /// /// Only return this service as valid when we're typing curly braces inside an interpolated string. /// Otherwise curly braces should be completed using the /// - public override async Task CanProvideBraceCompletionAsync(char brace, int openingPosition, Document document, CancellationToken cancellationToken) - => OpeningBrace == brace && await IsPositionInInterpolationContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(false); + public override bool CanProvideBraceCompletion(char brace, int openingPosition, ParsedDocument document, CancellationToken cancellationToken) + => OpeningBrace == brace && IsPositionInInterpolationContext(document, openingPosition); protected override bool IsValidOpenBraceTokenAtPosition(SourceText text, SyntaxToken token, int position) => IsValidOpeningBraceToken(token) && token.SpanStart == position; @@ -52,19 +52,17 @@ protected override bool IsValidClosingBraceToken(SyntaxToken token) /// /// Returns true when the input position could be starting an interpolation expression if a curly brace was typed. /// - public static async Task IsPositionInInterpolationContextAsync(Document document, int position, CancellationToken cancellationToken) + public static bool IsPositionInInterpolationContext(ParsedDocument document, int position) { // First, check to see if the character to the left of the position is an open curly. // If it is, we shouldn't complete because the user may be trying to escape a curly. // E.g. they are trying to type $"{{" - if (await CouldEscapePreviousOpenBraceAsync('{', position, document, cancellationToken).ConfigureAwait(false)) + if (CouldEscapePreviousOpenBrace('{', position, document.Text)) { return false; } - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var token = root.FindTokenOnLeftOfPosition(position); - + var token = document.Root.FindTokenOnLeftOfPosition(position); if (!token.Span.IntersectsWith(position)) { return false; diff --git a/src/Features/CSharp/Portable/BraceCompletion/LessAndGreaterThanBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/LessAndGreaterThanBraceCompletionService.cs index b5d9bb0ae160e..4fbf8cb446456 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/LessAndGreaterThanBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/LessAndGreaterThanBraceCompletionService.cs @@ -30,8 +30,8 @@ public LessAndGreaterThanBraceCompletionService() protected override char OpeningBrace => LessAndGreaterThan.OpenCharacter; protected override char ClosingBrace => LessAndGreaterThan.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken); protected override bool IsValidOpeningBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.LessThanToken); @@ -39,21 +39,20 @@ protected override bool IsValidOpeningBraceToken(SyntaxToken token) protected override bool IsValidClosingBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.GreaterThanToken); - protected override async ValueTask IsValidOpenBraceTokenAtPositionAsync(Document document, SyntaxToken token, int position, CancellationToken cancellationToken) + protected override Task IsValidOpenBraceTokenAtPositionAsync(Document document, SyntaxToken token, int position, CancellationToken cancellationToken) { // check what parser thinks about the newly typed "<" and only proceed if parser thinks it is "<" of // type argument or parameter list - return token.CheckParent(n => n.LessThanToken == token) || + if (token.CheckParent(n => n.LessThanToken == token) || token.CheckParent(n => n.LessThanToken == token) || - token.CheckParent(n => n.LessThanToken == token) || - await PossibleTypeArgumentAsync(document, token, cancellationToken).ConfigureAwait(false); - } + token.CheckParent(n => n.LessThanToken == token)) + { + return Task.FromResult(true); + } - private static async ValueTask PossibleTypeArgumentAsync(Document document, SyntaxToken token, CancellationToken cancellationToken) - { // type argument can be easily ambiguous with normal < operations if (token.Parent is not BinaryExpressionSyntax(SyntaxKind.LessThanExpression) node || node.OperatorToken != token) - return false; + return Task.FromResult(false); // type_argument_list only shows up in the following grammar construct: // @@ -63,11 +62,16 @@ private static async ValueTask PossibleTypeArgumentAsync(Document document // So if the prior token is not an identifier, this could not be a type-argument-list. var previousToken = token.GetPreviousToken(); if (previousToken.Parent is not IdentifierNameSyntax identifier) - return false; + return Task.FromResult(false); + + return IsSemanticTypeArgumentAsync(document, node.SpanStart, identifier, cancellationToken); - var semanticModel = await document.ReuseExistingSpeculativeModelAsync(node.SpanStart, cancellationToken).ConfigureAwait(false); - var info = semanticModel.GetSymbolInfo(identifier, cancellationToken); - return info.CandidateSymbols.Any(static s => s.GetArity() > 0); + static async Task IsSemanticTypeArgumentAsync(Document document, int position, IdentifierNameSyntax identifier, CancellationToken cancellationToken) + { + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); + var info = semanticModel.GetSymbolInfo(identifier, cancellationToken); + return info.CandidateSymbols.Any(static s => s.GetArity() > 0); + } } } } diff --git a/src/Features/CSharp/Portable/BraceCompletion/ParenthesisBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/ParenthesisBraceCompletionService.cs index 6728253f62f11..1668e648b7dcd 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/ParenthesisBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/ParenthesisBraceCompletionService.cs @@ -27,8 +27,8 @@ public ParenthesisBraceCompletionService() protected override char OpeningBrace => Parenthesis.OpenCharacter; protected override char ClosingBrace => Parenthesis.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken); protected override bool IsValidOpeningBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.OpenParenToken); diff --git a/src/Features/CSharp/Portable/BraceCompletion/StringLiteralBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/StringLiteralBraceCompletionService.cs index d6b2777dc1128..24c9f9e8a352a 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/StringLiteralBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/StringLiteralBraceCompletionService.cs @@ -28,18 +28,18 @@ public StringLiteralBraceCompletionService() protected override char OpeningBrace => DoubleQuote.OpenCharacter; protected override char ClosingBrace => DoubleQuote.CloseCharacter; - public override Task AllowOverTypeAsync(BraceCompletionContext context, CancellationToken cancellationToken) - => AllowOverTypeWithValidClosingTokenAsync(context, cancellationToken); + public override bool AllowOverType(BraceCompletionContext context, CancellationToken cancellationToken) + => AllowOverTypeWithValidClosingToken(context); - public override async Task CanProvideBraceCompletionAsync(char brace, int openingPosition, Document document, CancellationToken cancellationToken) + public override bool CanProvideBraceCompletion(char brace, int openingPosition, ParsedDocument document, CancellationToken cancellationToken) { // Only potentially valid for string literal completion if not in an interpolated string brace completion context. - if (OpeningBrace == brace && await InterpolatedStringBraceCompletionService.IsPositionInInterpolatedStringContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(false)) + if (OpeningBrace == brace && InterpolatedStringBraceCompletionService.IsPositionInInterpolatedStringContext(document, openingPosition, cancellationToken)) { return false; } - return await base.CanProvideBraceCompletionAsync(brace, openingPosition, document, cancellationToken).ConfigureAwait(false); + return base.CanProvideBraceCompletion(brace, openingPosition, document, cancellationToken); } protected override bool IsValidOpeningBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.StringLiteralToken); diff --git a/src/Features/CSharp/Portable/CodeRefactorings/ExtractClass/CSharpExtractClassCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/ExtractClass/CSharpExtractClassCodeRefactoringProvider.cs index 163e188c39d53..b860964e66efa 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/ExtractClass/CSharpExtractClassCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/ExtractClass/CSharpExtractClassCodeRefactoringProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -41,7 +42,7 @@ internal CSharpExtractClassCodeRefactoringProvider(IExtractClassOptionsService o return relaventNodes.FirstOrDefault(); } - protected override Task GetSelectedNodeAsync(CodeRefactoringContext context) - => NodeSelectionHelpers.GetSelectedDeclarationOrVariableAsync(context); + protected override Task> GetSelectedNodesAsync(CodeRefactoringContext context) + => NodeSelectionHelpers.GetSelectedDeclarationsOrVariablesAsync(context); } } diff --git a/src/Features/CSharp/Portable/CodeRefactorings/MoveStaticMembers/CSharpMoveStaticMembersRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/MoveStaticMembers/CSharpMoveStaticMembersRefactoringProvider.cs index b90f4cf55611f..c5e636d2a11ae 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/MoveStaticMembers/CSharpMoveStaticMembersRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/MoveStaticMembers/CSharpMoveStaticMembersRefactoringProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -20,7 +21,7 @@ public CSharpMoveStaticMembersRefactoringProvider() : base() { } - protected override Task GetSelectedNodeAsync(CodeRefactoringContext context) - => NodeSelectionHelpers.GetSelectedDeclarationOrVariableAsync(context); + protected override Task> GetSelectedNodesAsync(CodeRefactoringContext context) + => NodeSelectionHelpers.GetSelectedDeclarationsOrVariablesAsync(context); } } diff --git a/src/Features/CSharp/Portable/CodeRefactorings/NodeSelectionHelpers.cs b/src/Features/CSharp/Portable/CodeRefactorings/NodeSelectionHelpers.cs index f4b1718276817..c7fdef4585b25 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/NodeSelectionHelpers.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/NodeSelectionHelpers.cs @@ -4,22 +4,57 @@ #nullable disable +using System; +using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings { internal static class NodeSelectionHelpers { - internal static async Task GetSelectedDeclarationOrVariableAsync(CodeRefactoringContext context) + internal static async Task> GetSelectedDeclarationsOrVariablesAsync(CodeRefactoringContext context) { - // Consider: - // MemberDeclaration: member that can be declared in type (those are the ones we can pull up) - // VariableDeclaratorSyntax: for fields the MemberDeclaration can actually represent multiple declarations, e.g. `int a = 0, b = 1;`. - // ..Since the user might want to select & pull up only one of them (e.g. `int a = 0, [|b = 1|];` we also look for closest VariableDeclaratorSyntax. - return await context.TryGetRelevantNodeAsync().ConfigureAwait(false) as SyntaxNode ?? - await context.TryGetRelevantNodeAsync().ConfigureAwait(false); + var (document, span, cancellationToken) = context; + if (span.IsEmpty) + { + // if the span is empty then we are only selecting one "member" (which could include a field which declared multiple actual members) + // Consider: + // MemberDeclaration: member that can be declared in type (those are the ones we can pull up) + // VariableDeclaratorSyntax: for fields the MemberDeclaration can actually represent multiple declarations, e.g. `int a = 0, b = 1;`. + // ..Since the user might want to select & pull up only one of them (e.g. `int a = 0, [|b = 1|];` we also look for closest VariableDeclaratorSyntax. + var memberDeclaration = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); + if (memberDeclaration == null) + { + // could not find a member, we may be directly on a variable declaration + var varDeclarator = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); + return varDeclarator == null + ? ImmutableArray.Empty + : ImmutableArray.Create(varDeclarator); + } + else + { + return memberDeclaration switch + { + FieldDeclarationSyntax fieldDeclaration => fieldDeclaration.Declaration.Variables.AsImmutable(), + EventFieldDeclarationSyntax eventFieldDeclaration => eventFieldDeclaration.Declaration.Variables.AsImmutable(), + _ => ImmutableArray.Create(memberDeclaration), + }; + } + } + else + { + // if the span is non-empty, then we get potentially multiple members + // Note: even though this method handles the empty span case, we don't use it because it doesn't correctly + // pick up on keywords before the declaration, such as "public static int". + // We could potentially use it for every case if that behavior changes + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + return await CSharpSelectedMembers.Instance.GetSelectedMembersAsync(tree, span, allowPartialSelection: true, cancellationToken).ConfigureAwait(false); + } } } } diff --git a/src/Features/CSharp/Portable/CodeRefactorings/PullMemberUp/CSharpPullMemberUpCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/PullMemberUp/CSharpPullMemberUpCodeRefactoringProvider.cs index cccf4be8ca8aa..bcb349c494c74 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/PullMemberUp/CSharpPullMemberUpCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/PullMemberUp/CSharpPullMemberUpCodeRefactoringProvider.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; @@ -33,7 +34,7 @@ public CSharpPullMemberUpCodeRefactoringProvider() : this(service: null) { } - protected override Task GetSelectedNodeAsync(CodeRefactoringContext context) - => NodeSelectionHelpers.GetSelectedDeclarationOrVariableAsync(context); + protected override Task> GetSelectedNodesAsync(CodeRefactoringContext context) + => NodeSelectionHelpers.GetSelectedDeclarationsOrVariablesAsync(context); } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs index 2c74a4a4cc164..21c022e42f4eb 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs @@ -68,6 +68,7 @@ public KeywordCompletionProvider() new ExternKeywordRecommender(), new FalseKeywordRecommender(), new FieldKeywordRecommender(), + new FileKeywordRecommender(), new FinallyKeywordRecommender(), new FixedKeywordRecommender(), new FloatKeywordRecommender(), diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/Scripting/LoadDirectiveCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/Scripting/LoadDirectiveCompletionProvider.cs index 6917a710b961f..b84329b7276f0 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/Scripting/LoadDirectiveCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/Scripting/LoadDirectiveCompletionProvider.cs @@ -7,12 +7,13 @@ using System.Threading; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { [ExportCompletionProvider(nameof(LoadDirectiveCompletionProvider), LanguageNames.CSharp)] - [ExtensionOrder(After = nameof(FunctionPointerUnmanagedCallingConventionCompletionProvider))] + [ExtensionOrder(After = nameof(CSharpSnippetCompletionProvider))] [Shared] internal sealed class LoadDirectiveCompletionProvider : AbstractLoadDirectiveCompletionProvider { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.cs new file mode 100644 index 0000000000000..cb1c3932f6707 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.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.Composition; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers.Snippets; +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Snippets; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets +{ + [ExportCompletionProvider(nameof(CSharpSnippetCompletionProvider), LanguageNames.CSharp)] + [ExtensionOrder(After = nameof(FunctionPointerUnmanagedCallingConventionCompletionProvider))] + [Shared] + internal class CSharpSnippetCompletionProvider : AbstractSnippetCompletionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpSnippetCompletionProvider() + { + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.cs new file mode 100644 index 0000000000000..73510f4b46dd1 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.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.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders; + +internal class FileKeywordRecommender : AbstractSyntacticSingleKeywordRecommender +{ + private static readonly ISet s_validModifiers = SyntaxKindSet.AllMemberModifiers + .Where(s => s != SyntaxKind.FileKeyword && !SyntaxFacts.IsAccessibilityModifier(s)) + .ToSet(); + + public FileKeywordRecommender() + : base(SyntaxKind.FileKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.ContainingTypeDeclaration == null + && context.IsTypeDeclarationContext(s_validModifiers, SyntaxKindSet.AllTypeDeclarations, canBePartial: true, cancellationToken); + } +} diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NameOfKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NameOfKeywordRecommender.cs index 35616bb9637ef..a925b3f4aad22 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NameOfKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NameOfKeywordRecommender.cs @@ -23,15 +23,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context context.IsAnyExpressionContext || context.IsStatementContext || context.IsGlobalStatementContext || - IsAttributeArgumentContext(context) || context.LeftToken.IsInCastExpressionTypeWhereExpressionIsMissingOrInNextLine(); } - - private static bool IsAttributeArgumentContext(CSharpSyntaxContext context) - { - return - context.IsAnyExpressionContext && - context.LeftToken.GetAncestor() != null; - } } } diff --git a/src/Features/CSharp/Portable/ConvertToRawString/ConvertRegularStringToRawStringCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertToRawString/ConvertRegularStringToRawStringCodeRefactoringProvider.cs index c409bc37868ef..9a55292210b67 100644 --- a/src/Features/CSharp/Portable/ConvertToRawString/ConvertRegularStringToRawStringCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertToRawString/ConvertRegularStringToRawStringCodeRefactoringProvider.cs @@ -188,7 +188,7 @@ private static async Task UpdateDocumentAsync( Contract.ThrowIfFalse(span.IntersectsWith(token.Span)); Contract.ThrowIfFalse(token.Kind() == SyntaxKind.StringLiteralToken); - var replacement = GetReplacementToken(document, token, kind, options, cancellationToken); + var replacement = await GetReplacementTokenAsync(document, token, kind, options, cancellationToken).ConfigureAwait(false); return document.WithSyntaxRoot(root.ReplaceToken(token, replacement)); } @@ -228,7 +228,7 @@ protected override async Task FixAllAsync( if (!hasMatchingKind) continue; - var replacement = GetReplacementToken(document, stringLiteral, kind, options, cancellationToken); + var replacement = await GetReplacementTokenAsync(document, stringLiteral, kind, options, cancellationToken).ConfigureAwait(false); tokenReplacementMap.Add(stringLiteral, replacement); } } @@ -237,11 +237,11 @@ protected override async Task FixAllAsync( editor.ReplaceNode(editor.OriginalRoot, newRoot); } - private static SyntaxToken GetReplacementToken( + private static async ValueTask GetReplacementTokenAsync( Document document, SyntaxToken token, ConvertToRawKind kind, - SyntaxFormattingOptions options, + SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) { var characters = CSharpVirtualCharService.Instance.TryConvertToVirtualChars(token); @@ -251,9 +251,15 @@ private static SyntaxToken GetReplacementToken( if (kind == ConvertToRawKind.MultiLineWithoutLeadingWhitespace) characters = CleanupWhitespace(characters); - return kind == ConvertToRawKind.SingleLine - ? ConvertToSingleLineRawString(token, characters) - : ConvertToMultiLineRawIndentedString(document, token, options, characters, cancellationToken); + if (kind == ConvertToRawKind.SingleLine) + { + return ConvertToSingleLineRawString(token, characters); + } + + var indentationOptions = new IndentationOptions(formattingOptions); + var parsedDocument = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var indentation = token.GetPreferredIndentation(parsedDocument, indentationOptions, cancellationToken); + return ConvertToMultiLineRawIndentedString(indentation, token, formattingOptions, characters); } private static VirtualCharSequence CleanupWhitespace(VirtualCharSequence characters) @@ -380,20 +386,15 @@ private static bool AllWhitespace(VirtualCharSequence line) } private static SyntaxToken ConvertToMultiLineRawIndentedString( - Document document, + string indentation, SyntaxToken token, SyntaxFormattingOptions formattingOptions, - VirtualCharSequence characters, - CancellationToken cancellationToken) + VirtualCharSequence characters) { // Have to make sure we have a delimiter longer than any quote sequence in the string. var longestQuoteSequence = GetLongestQuoteSequence(characters); var quoteDelimeterCount = Math.Max(3, longestQuoteSequence + 1); - // Auto-formatting options are not relevant since they only control behavior on typing. - var indentationOptions = new IndentationOptions(formattingOptions); - var indentation = token.GetPreferredIndentation(document, indentationOptions, cancellationToken); - using var _ = PooledStringBuilder.GetInstance(out var builder); builder.Append('"', quoteDelimeterCount); diff --git a/src/Features/CSharp/Portable/Debugging/LocationInfoGetter.cs b/src/Features/CSharp/Portable/Debugging/LocationInfoGetter.cs index b78c8debe8ee4..d037fd3a1e607 100644 --- a/src/Features/CSharp/Portable/Debugging/LocationInfoGetter.cs +++ b/src/Features/CSharp/Portable/Debugging/LocationInfoGetter.cs @@ -50,10 +50,7 @@ internal static async Task GetInfoAsync(Document document, in } } - if (fieldDeclarator == null) - { - fieldDeclarator = variableDeclarators.Count > 0 ? variableDeclarators[0] : null; - } + fieldDeclarator ??= variableDeclarators.Count > 0 ? variableDeclarators[0] : null; } var name = syntaxFactsService.GetDisplayName(fieldDeclarator ?? memberDeclaration, diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index 729486de888cc..c25a9d227f638 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -62,10 +62,7 @@ protected override async Task> GetAdditionalReferencesA if (type.IsVar) { - if (semanticModel == null) - { - semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - } + semanticModel ??= await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var boundSymbol = semanticModel.GetSymbolInfo(type, cancellationToken).Symbol; boundSymbol = boundSymbol?.OriginalDefinition; diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs index 30d95a0663b82..730ceb2d787c0 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs @@ -170,22 +170,13 @@ container is ConstructorDeclarationSyntax || protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite() { var scope = (SyntaxNode)CSharpSelectionResult.GetContainingScopeOf(); - if (scope == null) - { - scope = CSharpSelectionResult.GetContainingScopeOf(); - } + scope ??= CSharpSelectionResult.GetContainingScopeOf(); - if (scope == null) - { - scope = CSharpSelectionResult.GetContainingScopeOf(); - } + scope ??= CSharpSelectionResult.GetContainingScopeOf(); - if (scope == null) - { - // This is similar to FieldDeclaration case but we only want to do this - // if the member has an expression body. - scope = CSharpSelectionResult.GetContainingScopeOf().Parent; - } + // This is similar to FieldDeclaration case but we only want to do this + // if the member has an expression body. + scope ??= CSharpSelectionResult.GetContainingScopeOf().Parent; return scope; } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs new file mode 100644 index 0000000000000..1ac7d20c24337 --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs @@ -0,0 +1,43 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets +{ + [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] + internal class CSharpConsoleSnippetProvider : AbstractConsoleSnippetProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpConsoleSnippetProvider() + { + } + + protected override SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken token) + { + var node = token.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + return node; + } + } +} diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs new file mode 100644 index 0000000000000..d9893eac487ae --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -0,0 +1,44 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets +{ + [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] + internal class CSharpIfSnippetProvider : AbstractIfSnippetProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpIfSnippetProvider() + { + } + + protected override void GetIfStatementConditionAndCursorPosition(SyntaxNode node, out SyntaxNode condition, out int cursorPositionNode) + { + var ifStatement = (IfStatementSyntax)node; + condition = ifStatement.Condition; + cursorPositionNode = ifStatement.Statement.SpanStart + 1; + } + } +} diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs new file mode 100644 index 0000000000000..6f4f796207d72 --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs @@ -0,0 +1,34 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets +{ + [ExportLanguageService(typeof(ISnippetService), LanguageNames.CSharp), Shared] + internal class CSharpSnippetService : AbstractSnippetService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpSnippetService([ImportMany] IEnumerable> snippetProviders) + : base(snippetProviders) + { + } + } +} diff --git a/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs b/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs index c6e2382ac162a..fc6a837234578 100644 --- a/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs +++ b/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs @@ -21,16 +21,12 @@ private sealed class InterpolatedStringSplitter : StringSplitter private readonly InterpolatedStringExpressionSyntax _interpolatedStringExpression; public InterpolatedStringSplitter( - Document document, + ParsedDocument document, int position, - SyntaxNode root, - SourceText sourceText, InterpolatedStringExpressionSyntax interpolatedStringExpression, IndentationOptions options, - bool useTabs, - int tabSize, CancellationToken cancellationToken) - : base(document, position, root, sourceText, options, useTabs, tabSize, cancellationToken) + : base(document, position, options, cancellationToken) { _interpolatedStringExpression = interpolatedStringExpression; } @@ -88,7 +84,7 @@ protected override BinaryExpressionSyntax CreateSplitString() private InterpolatedStringTextSyntax CreateInterpolatedStringText(int start, int end) { - var content = SourceText.ToString(TextSpan.FromBounds(start, end)); + var content = Document.Text.ToString(TextSpan.FromBounds(start, end)); return SyntaxFactory.InterpolatedStringText( SyntaxFactory.Token( leading: default, diff --git a/src/Features/CSharp/Portable/SplitStringLiteral/SimpleStringSplitter.cs b/src/Features/CSharp/Portable/SplitStringLiteral/SimpleStringSplitter.cs index d3a6e8307afe3..26355e115e175 100644 --- a/src/Features/CSharp/Portable/SplitStringLiteral/SimpleStringSplitter.cs +++ b/src/Features/CSharp/Portable/SplitStringLiteral/SimpleStringSplitter.cs @@ -20,10 +20,12 @@ private sealed class SimpleStringSplitter : StringSplitter private readonly SyntaxToken _token; public SimpleStringSplitter( - Document document, int position, - SyntaxNode root, SourceText sourceText, SyntaxToken token, - in IndentationOptions options, bool useTabs, int tabSize, CancellationToken cancellationToken) - : base(document, position, root, sourceText, options, useTabs, tabSize, cancellationToken) + ParsedDocument document, + int position, + SyntaxToken token, + in IndentationOptions options, + CancellationToken cancellationToken) + : base(document, position, options, cancellationToken) { _token = token; } @@ -43,13 +45,13 @@ private bool CursorIsAfterQuotesInUtf8String() protected override BinaryExpressionSyntax CreateSplitString() { // TODO(cyrusn): Deal with the positoin being after a \ character - var prefix = SourceText.GetSubText(TextSpan.FromBounds(_token.SpanStart, CursorPosition)).ToString(); - var suffix = SourceText.GetSubText(TextSpan.FromBounds(CursorPosition, _token.Span.End)).ToString(); + var prefix = Document.Text.GetSubText(TextSpan.FromBounds(_token.SpanStart, CursorPosition)).ToString(); + var suffix = Document.Text.GetSubText(TextSpan.FromBounds(CursorPosition, _token.Span.End)).ToString(); // If we're spliting a UTF-8 string we need to keep the u8 suffix on the first part. We copy whatever // the user had on the second part, for consistency. var firstTokenSuffix = _token.Kind() == SyntaxKind.Utf8StringLiteralToken - ? SourceText.GetSubText(TextSpan.FromBounds(_token.Span.End - "u8".Length, _token.Span.End)).ToString() + ? Document.Text.GetSubText(TextSpan.FromBounds(_token.Span.End - "u8".Length, _token.Span.End)).ToString() : ""; var firstToken = SyntaxFactory.Token( diff --git a/src/Features/CSharp/Portable/SplitStringLiteral/StringSplitter.cs b/src/Features/CSharp/Portable/SplitStringLiteral/StringSplitter.cs index bde21447daf39..d2c6f1bbb94c8 100644 --- a/src/Features/CSharp/Portable/SplitStringLiteral/StringSplitter.cs +++ b/src/Features/CSharp/Portable/SplitStringLiteral/StringSplitter.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.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,47 +22,38 @@ internal abstract partial class StringSplitter SyntaxKind.PlusToken, SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed)); - protected readonly Document Document; + protected readonly ParsedDocument Document; protected readonly int CursorPosition; - protected readonly SourceText SourceText; - protected readonly SyntaxNode Root; protected readonly IndentationOptions Options; - protected readonly int TabSize; - protected readonly bool UseTabs; protected readonly CancellationToken CancellationToken; public StringSplitter( - Document document, int position, - SyntaxNode root, SourceText sourceText, - in IndentationOptions options, bool useTabs, int tabSize, + ParsedDocument document, int position, + in IndentationOptions options, CancellationToken cancellationToken) { Document = document; CursorPosition = position; - Root = root; - SourceText = sourceText; - UseTabs = useTabs; - TabSize = tabSize; Options = options; CancellationToken = cancellationToken; } - public static StringSplitter TryCreate( - Document document, int position, - in IndentationOptions options, bool useTabs, int tabSize, + protected int TabSize => Options.FormattingOptions.TabSize; + protected bool UseTabs => Options.FormattingOptions.UseTabs; + + public static StringSplitter? TryCreate( + ParsedDocument document, int position, + in IndentationOptions options, CancellationToken cancellationToken) { - var root = document.GetSyntaxRootSynchronously(cancellationToken); - var sourceText = root.SyntaxTree.GetText(cancellationToken); - - var token = root.FindToken(position); + var token = document.Root.FindToken(position); if (token.IsKind(SyntaxKind.StringLiteralToken) || token.IsKind(SyntaxKind.Utf8StringLiteralToken)) { return new SimpleStringSplitter( - document, position, root, - sourceText, token, options, useTabs, tabSize, + document, position, + token, options, cancellationToken); } @@ -71,15 +61,15 @@ public static StringSplitter TryCreate( if (interpolatedStringExpression != null) { return new InterpolatedStringSplitter( - document, position, root, - sourceText, interpolatedStringExpression, - options, useTabs, tabSize, cancellationToken); + document, position, + interpolatedStringExpression, + options, cancellationToken); } return null; } - private static InterpolatedStringExpressionSyntax TryGetInterpolatedStringExpression( + private static InterpolatedStringExpressionSyntax? TryGetInterpolatedStringExpression( SyntaxToken token, int position) { if (token.IsKind(SyntaxKind.InterpolatedStringTextToken) || @@ -107,55 +97,53 @@ private static bool IsInterpolationOpenBrace(SyntaxToken token, int position) protected abstract BinaryExpressionSyntax CreateSplitString(); - public bool TrySplit(out Document newDocument, out int newPosition) + public bool TrySplit([NotNullWhen(true)] out SyntaxNode? newRoot, out int newPosition) { var nodeToReplace = GetNodeToReplace(); if (CursorPosition <= nodeToReplace.SpanStart || CursorPosition >= nodeToReplace.Span.End) { - newDocument = null; + newRoot = null; newPosition = 0; return false; } if (!CheckToken()) { - newDocument = null; + newRoot = null; newPosition = 0; return false; } - (newDocument, newPosition) = SplitString(); + (newRoot, newPosition) = SplitString(); return true; } - private (Document document, int caretPosition) SplitString() + private (SyntaxNode root, int caretPosition) SplitString() { var splitString = CreateSplitString(); var nodeToReplace = GetNodeToReplace(); - var newRoot = Root.ReplaceNode(nodeToReplace, splitString); + var newRoot = Document.Root.ReplaceNode(nodeToReplace, splitString); var rightExpression = newRoot.GetAnnotatedNodes(RightNodeAnnotation).Single(); var indentString = GetIndentString(newRoot); var newRightExpression = rightExpression.WithLeadingTrivia(SyntaxFactory.ElasticWhitespace(indentString)); var newRoot2 = newRoot.ReplaceNode(rightExpression, newRightExpression); - var newDocument2 = Document.WithSyntaxRoot(newRoot2); - return (newDocument2, rightExpression.Span.Start + indentString.Length + StringOpenQuoteLength()); + return (newRoot2, rightExpression.Span.Start + indentString.Length + StringOpenQuoteLength()); } private string GetIndentString(SyntaxNode newRoot) { - var newDocument = Document.WithSyntaxRoot(newRoot); - - var indentationService = newDocument.GetLanguageService(); - var originalLineNumber = SourceText.Lines.GetLineFromPosition(CursorPosition).LineNumber; + var indentationService = Document.LanguageServices.GetRequiredService(); + var originalLineNumber = Document.Text.Lines.GetLineFromPosition(CursorPosition).LineNumber; + var newDocument = Document.WithChangedRoot(newRoot, CancellationToken); var desiredIndentation = indentationService.GetIndentation( newDocument, originalLineNumber + 1, Options, CancellationToken); - var newSourceText = newDocument.GetSyntaxRootSynchronously(CancellationToken).SyntaxTree.GetText(CancellationToken); + var newSourceText = newDocument.Text; var baseLine = newSourceText.Lines.GetLineFromPosition(desiredIndentation.BasePosition); var baseOffsetInLineInPositions = desiredIndentation.BasePosition - baseLine.Start; diff --git a/src/Features/Core/Portable/BraceCompletion/AbstractBraceCompletionService.cs b/src/Features/Core/Portable/BraceCompletion/AbstractBraceCompletionService.cs index bbc3470a36615..f3790f36a1e94 100644 --- a/src/Features/Core/Portable/BraceCompletion/AbstractBraceCompletionService.cs +++ b/src/Features/Core/Portable/BraceCompletion/AbstractBraceCompletionService.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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Indentation; @@ -35,55 +36,46 @@ internal abstract class AbstractBraceCompletionService : IBraceCompletionService /// protected abstract bool IsValidClosingBraceToken(SyntaxToken token); - public abstract Task AllowOverTypeAsync(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken); + public abstract bool AllowOverType(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken); - public async Task GetBraceCompletionAsync(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken) + public Task HasBraceCompletionAsync(BraceCompletionContext context, Document document, CancellationToken cancellationToken) { - var closingPoint = braceCompletionContext.ClosingPoint; - if (closingPoint < 1) - return null; - - var openingPoint = braceCompletionContext.OpeningPoint; - var document = braceCompletionContext.Document; - - var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (sourceText[openingPoint] != OpeningBrace) - return null; - - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var token = root.FindToken(openingPoint, findInsideTrivia: true); - - if (NeedsSemantics) + if (!context.HasCompletionForOpeningBrace(OpeningBrace)) { - // Pass along a document with frozen partial semantics. Brace completion is a highly latency sensitive - // operation. We don't want to wait on things like source generators to figure things out. - var validOpeningPoint = await IsValidOpenBraceTokenAtPositionAsync( - document.WithFrozenPartialSemantics(cancellationToken), token, openingPoint, cancellationToken).ConfigureAwait(false); - if (!validOpeningPoint) - return null; + return Task.FromResult(false); } - else + + var openingToken = context.GetOpeningToken(); + if (!NeedsSemantics) { - var validOpeningPoint = IsValidOpenBraceTokenAtPosition(sourceText, token, openingPoint); - if (!validOpeningPoint) - return null; + return Task.FromResult(IsValidOpenBraceTokenAtPosition(context.Document.Text, openingToken, context.OpeningPoint)); } + // Pass along a document with frozen partial semantics. Brace completion is a highly latency sensitive + // operation. We don't want to wait on things like source generators to figure things out. + return IsValidOpenBraceTokenAtPositionAsync(document.WithFrozenPartialSemantics(cancellationToken), openingToken, context.OpeningPoint, cancellationToken); + } + + public BraceCompletionResult GetBraceCompletion(BraceCompletionContext context) + { + Debug.Assert(context.HasCompletionForOpeningBrace(OpeningBrace)); + + var closingPoint = context.ClosingPoint; var braceTextEdit = new TextChange(TextSpan.FromBounds(closingPoint, closingPoint), ClosingBrace.ToString()); // The caret location should be in between the braces. - var originalOpeningLinePosition = sourceText.Lines.GetLinePosition(openingPoint); + var originalOpeningLinePosition = context.Document.Text.Lines.GetLinePosition(context.OpeningPoint); var caretLocation = new LinePosition(originalOpeningLinePosition.Line, originalOpeningLinePosition.Character + 1); return new BraceCompletionResult(ImmutableArray.Create(braceTextEdit), caretLocation); } - public virtual Task GetTextChangesAfterCompletionAsync(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken) - => SpecializedTasks.Default(); + public virtual BraceCompletionResult? GetTextChangesAfterCompletion(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken) + => null; - public virtual Task GetTextChangeAfterReturnAsync(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken) - => SpecializedTasks.Default(); + public virtual BraceCompletionResult? GetTextChangeAfterReturn(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken) + => null; - public virtual async Task CanProvideBraceCompletionAsync(char brace, int openingPosition, Document document, CancellationToken cancellationToken) + public virtual bool CanProvideBraceCompletion(char brace, int openingPosition, ParsedDocument document, CancellationToken cancellationToken) { if (OpeningBrace != brace) { @@ -91,17 +83,15 @@ public virtual async Task CanProvideBraceCompletionAsync(char brace, int o } // check that the user is not typing in a string literal or comment - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var syntaxFactsService = document.GetRequiredLanguageService(); + var syntaxFactsService = document.LanguageServices.GetRequiredService(); - return !syntaxFactsService.IsInNonUserCode(tree, openingPosition, cancellationToken); + return !syntaxFactsService.IsInNonUserCode(document.SyntaxTree, openingPosition, cancellationToken); } - public async Task GetCompletedBraceContextAsync(Document document, int caretLocation, CancellationToken cancellationToken) + public BraceCompletionContext? GetCompletedBraceContext(ParsedDocument document, int caretLocation) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var leftToken = root.FindTokenOnLeftOfPosition(caretLocation); - var rightToken = root.FindTokenOnRightOfPosition(caretLocation); + var leftToken = document.Root.FindTokenOnLeftOfPosition(caretLocation); + var rightToken = document.Root.FindTokenOnRightOfPosition(caretLocation); if (IsValidOpeningBraceToken(leftToken) && IsValidClosingBraceToken(rightToken)) { @@ -114,7 +104,7 @@ public virtual async Task CanProvideBraceCompletionAsync(char brace, int o /// /// Only called if returns true; /// - protected virtual ValueTask IsValidOpenBraceTokenAtPositionAsync(Document document, SyntaxToken token, int position, CancellationToken cancellationToken) + protected virtual Task IsValidOpenBraceTokenAtPositionAsync(Document document, SyntaxToken token, int position, CancellationToken cancellationToken) { // Subclass should have overridden this. throw ExceptionUtilities.Unreachable; @@ -130,25 +120,25 @@ protected virtual bool IsValidOpenBraceTokenAtPosition(SourceText text, SyntaxTo /// /// Returns true when the current position is inside user code (e.g. not strings) and the closing token /// matches the expected closing token for this brace completion service. - /// Helper method used by implementations. + /// Helper method used by implementations. /// - protected async Task AllowOverTypeInUserCodeWithValidClosingTokenAsync(BraceCompletionContext context, CancellationToken cancellationToken) + protected bool AllowOverTypeInUserCodeWithValidClosingToken(BraceCompletionContext context, CancellationToken cancellationToken) { - var tree = await context.Document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var syntaxFactsService = context.Document.GetRequiredLanguageService(); + var tree = context.Document.SyntaxTree; + var syntaxFactsService = context.Document.LanguageServices.GetRequiredService(); return !syntaxFactsService.IsInNonUserCode(tree, context.CaretLocation, cancellationToken) - && await CheckClosingTokenKindAsync(context.Document, context.ClosingPoint, cancellationToken).ConfigureAwait(false); + && CheckClosingTokenKind(context.Document, context.ClosingPoint); } /// /// Returns true when the closing token matches the expected closing token for this brace completion service. - /// Used by implementations + /// Used by implementations /// when the over type could be triggered from outside of user code (e.g. overtyping end quotes in a string). /// - protected Task AllowOverTypeWithValidClosingTokenAsync(BraceCompletionContext context, CancellationToken cancellationToken) + protected bool AllowOverTypeWithValidClosingToken(BraceCompletionContext context) { - return CheckClosingTokenKindAsync(context.Document, context.ClosingPoint, cancellationToken); + return CheckClosingTokenKind(context.Document, context.ClosingPoint); } protected static bool ParentIsSkippedTokensTriviaOrNull(ISyntaxFacts syntaxFacts, SyntaxToken token) @@ -157,10 +147,9 @@ protected static bool ParentIsSkippedTokensTriviaOrNull(ISyntaxFacts syntaxFacts /// /// Checks that the token at the closing position is a valid closing token. /// - private async Task CheckClosingTokenKindAsync(Document document, int closingPosition, CancellationToken cancellationToken) + private bool CheckClosingTokenKind(ParsedDocument document, int closingPosition) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var closingToken = root.FindTokenFromEnd(closingPosition, includeZeroWidth: false, findInsideTrivia: true); + var closingToken = document.Root.FindTokenFromEnd(closingPosition, includeZeroWidth: false, findInsideTrivia: true); return IsValidClosingBraceToken(closingToken); } @@ -205,9 +194,8 @@ public static class SingleQuote /// escape a previously inserted opening brace. /// E.g. they are trying to type $"{{" /// - protected static async Task CouldEscapePreviousOpenBraceAsync(char openingBrace, int position, Document document, CancellationToken cancellationToken) + protected static bool CouldEscapePreviousOpenBrace(char openingBrace, int position, SourceText text) { - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var index = position - 1; var openBraceCount = 0; while (index >= 0) diff --git a/src/Features/Core/Portable/BraceCompletion/IBraceCompletionService.cs b/src/Features/Core/Portable/BraceCompletion/IBraceCompletionService.cs index 0da69a7acabd7..8828c20ac76df 100644 --- a/src/Features/Core/Portable/BraceCompletion/IBraceCompletionService.cs +++ b/src/Features/Core/Portable/BraceCompletion/IBraceCompletionService.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.BraceCompletion { - internal interface IBraceCompletionService : ILanguageService + internal interface IBraceCompletionService { /// /// Checks if this brace completion service should be the service used to provide brace completions at @@ -30,40 +30,46 @@ internal interface IBraceCompletionService : ILanguageService /// /// The document to insert the brace at the position. /// A cancellation token. - Task CanProvideBraceCompletionAsync(char brace, int openingPosition, Document document, CancellationToken cancellationToken); + bool CanProvideBraceCompletion(char brace, int openingPosition, ParsedDocument document, CancellationToken cancellationToken); + + /// + /// True if is available in the given . + /// Completes synchronously unless the service needs Semantic Model to determine the brace completion result. + /// + Task HasBraceCompletionAsync(BraceCompletionContext context, Document document, CancellationToken cancellationToken); /// /// Returns the text change to add the closing brace given the context. /// - Task GetBraceCompletionAsync(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken); + BraceCompletionResult GetBraceCompletion(BraceCompletionContext braceCompletionContext); /// /// Returns any text changes that need to be made after adding the closing brace. /// /// - /// This cannot be merged with + /// This cannot be merged with /// as we need to swap the editor tracking mode of the closing point from positive to negative /// in BraceCompletionSessionProvider.BraceCompletionSession.Start after completing the brace and before /// doing any kind of formatting on it. So these must be two distinct steps until we fully move to LSP. /// - Task GetTextChangesAfterCompletionAsync(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken); + BraceCompletionResult? GetTextChangesAfterCompletion(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken); /// /// Get any text changes that should be applied after the enter key is typed inside a brace completion context. /// - Task GetTextChangeAfterReturnAsync(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken); + BraceCompletionResult? GetTextChangeAfterReturn(BraceCompletionContext braceCompletionContext, IndentationOptions options, CancellationToken cancellationToken); /// /// Returns the brace completion context if the caret is located between an already completed /// set of braces with only whitespace in between. /// - Task GetCompletedBraceContextAsync(Document document, int caretLocation, CancellationToken cancellationToken); + BraceCompletionContext? GetCompletedBraceContext(ParsedDocument document, int caretLocation); /// /// Returns true if over typing should be allowed given the caret location and completed pair of braces. /// For example some providers allow over typing in non-user code and others do not. /// - Task AllowOverTypeAsync(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken); + bool AllowOverType(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken); } internal readonly struct BraceCompletionResult @@ -93,7 +99,7 @@ public BraceCompletionResult(ImmutableArray textChanges, LinePositio internal readonly struct BraceCompletionContext { - public Document Document { get; } + public ParsedDocument Document { get; } public int OpeningPoint { get; } @@ -101,12 +107,18 @@ internal readonly struct BraceCompletionContext public int CaretLocation { get; } - public BraceCompletionContext(Document document, int openingPoint, int closingPoint, int caretLocation) + public BraceCompletionContext(ParsedDocument document, int openingPoint, int closingPoint, int caretLocation) { Document = document; OpeningPoint = openingPoint; ClosingPoint = closingPoint; CaretLocation = caretLocation; } + + public bool HasCompletionForOpeningBrace(char openingBrace) + => ClosingPoint >= 1 && Document.Text[OpeningPoint] == openingBrace; + + public SyntaxToken GetOpeningToken() + => Document.Root.FindToken(OpeningPoint, findInsideTrivia: true); } } diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs index 9a8a50f732428..a24945400a524 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs @@ -332,11 +332,8 @@ private async Task GetSuppressionTargetInfoAsync(Document } } - if (targetSymbol == null) - { - // Outside of a member declaration, suppress diagnostic for the entire assembly. - targetSymbol = semanticModel.Compilation.Assembly; - } + // Outside of a member declaration, suppress diagnostic for the entire assembly. + targetSymbol ??= semanticModel.Compilation.Assembly; return new SuppressionTargetInfo() { TargetSymbol = targetSymbol, NodeWithTokens = nodeWithTokens, StartToken = startToken, EndToken = endToken, TargetMemberNode = targetMemberNode }; } diff --git a/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs b/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs index fe5e464a41d8b..f54277fe8a1b8 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs @@ -194,10 +194,7 @@ private static SyntaxNode GetEnclosingCodeElementNode(Document document, SyntaxT } } - if (node == null) - { - node = token.Parent; - } + node ??= token.Parent; return langServices.GetDisplayNode(node); } diff --git a/src/Features/Core/Portable/CommentSelection/AbstractCommentSelectionService.cs b/src/Features/Core/Portable/CommentSelection/AbstractCommentSelectionService.cs index 9d779836ebe77..644ae654ab0fd 100644 --- a/src/Features/Core/Portable/CommentSelection/AbstractCommentSelectionService.cs +++ b/src/Features/Core/Portable/CommentSelection/AbstractCommentSelectionService.cs @@ -20,17 +20,9 @@ internal abstract class AbstractCommentSelectionService : ICommentSelectionServi public abstract string SingleLineCommentString { get; } public abstract bool SupportsBlockComment { get; } - public Task FormatAsync(Document document, ImmutableArray changes, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) - { - var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); - var formattingSpans = changes.Select(s => CommonFormattingHelpers.GetFormattingSpan(root, s)); - - return Formatter.FormatAsync(document, formattingSpans, formattingOptions, rules: null, cancellationToken); - } - - public Task GetInfoAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) - => Task.FromResult(SupportsBlockComment - ? new CommentSelectionInfo(true, SupportsBlockComment, SingleLineCommentString, BlockCommentStartString, BlockCommentEndString) - : new CommentSelectionInfo(true, SupportsBlockComment, SingleLineCommentString, "", "")); + public CommentSelectionInfo GetInfo() + => SupportsBlockComment + ? new(supportsSingleLineComment: true, SupportsBlockComment, SingleLineCommentString, BlockCommentStartString, BlockCommentEndString) + : new(supportsSingleLineComment: true, SupportsBlockComment, SingleLineCommentString, blockCommentStartString: "", blockCommentEndString: ""); } } diff --git a/src/Features/Core/Portable/CommentSelection/ICommentSelectionService.cs b/src/Features/Core/Portable/CommentSelection/ICommentSelectionService.cs index 49858bbf8a298..8e259cc0b4ff0 100644 --- a/src/Features/Core/Portable/CommentSelection/ICommentSelectionService.cs +++ b/src/Features/Core/Portable/CommentSelection/ICommentSelectionService.cs @@ -13,8 +13,6 @@ namespace Microsoft.CodeAnalysis.CommentSelection { internal interface ICommentSelectionService : ILanguageService { - Task GetInfoAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken); - - Task FormatAsync(Document document, ImmutableArray changes, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken); + CommentSelectionInfo GetInfo(); } } diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index 4c7f456992d04..1e59751452d00 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -3,6 +3,7 @@ // 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 Microsoft.CodeAnalysis.Text; @@ -40,8 +41,16 @@ public sealed class CompletionChange /// public bool IncludesCommitCharacter { get; } + internal ImmutableDictionary Properties { get; } + private CompletionChange( TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter) + : this(textChange, textChanges, newPosition, includesCommitCharacter, ImmutableDictionary.Empty) + { + } + + private CompletionChange( + TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, ImmutableDictionary properties) { TextChange = textChange; NewPosition = newPosition; @@ -49,6 +58,7 @@ private CompletionChange( TextChanges = textChanges.NullToEmpty(); if (TextChanges.IsEmpty) TextChanges = ImmutableArray.Create(textChange); + Properties = properties; } /// @@ -97,6 +107,16 @@ public static CompletionChange Create( return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter); } + internal static CompletionChange Create( + TextChange textChange, + ImmutableArray textChanges, + ImmutableDictionary properties, + int? newPosition, + bool includesCommitCharacter) + { + return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter, properties); + } + /// /// Creates a copy of this with the property changed. /// diff --git a/src/Features/Core/Portable/Completion/CompletionList.cs b/src/Features/Core/Portable/Completion/CompletionList.cs index 9de387eb48a68..76e19071018b6 100644 --- a/src/Features/Core/Portable/Completion/CompletionList.cs +++ b/src/Features/Core/Portable/Completion/CompletionList.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -21,6 +22,8 @@ public sealed class CompletionList /// /// The completion items to present to the user. /// + [Obsolete($"This property is obsolete. Use {nameof(ItemsList)} instead", error: false)] + [EditorBrowsable(EditorBrowsableState.Never)] public ImmutableArray Items => _lazyItems.Value; /// @@ -28,7 +31,7 @@ public sealed class CompletionList /// This property is preferred over `Items` because of the flexibility it provides. /// For example, the list can be backed by types like SegmentedList to avoid LOH allocations. /// - internal IReadOnlyList ItemsList { get; } + public IReadOnlyList ItemsList { get; } /// /// The span of the syntax element at the caret position when the was created. diff --git a/src/Features/Core/Portable/Completion/CompletionOptions.cs b/src/Features/Core/Portable/Completion/CompletionOptions.cs index 7642ccd08faa0..a54fef950c650 100644 --- a/src/Features/Core/Portable/Completion/CompletionOptions.cs +++ b/src/Features/Core/Portable/Completion/CompletionOptions.cs @@ -27,6 +27,8 @@ internal sealed record class CompletionOptions public bool UpdateImportCompletionCacheInBackground { get; init; } = false; public bool FilterOutOfScopeLocals { get; init; } = true; public bool ShowXmlDocCommentCompletion { get; init; } = true; + public bool? ShowNewSnippetExperience { get; init; } = null; + public bool SnippetCompletion { get; init; } = false; public ExpandedCompletionMode ExpandedCompletionBehavior { get; init; } = ExpandedCompletionMode.AllItems; public NamingStylePreferences? NamingStyleFallbackOptions { get; init; } = null; @@ -47,5 +49,16 @@ public bool ShouldShowItemsFromUnimportNamspaces() // Don't trigger import completion if the option value is "default" and the experiment is disabled for the user. return ShowItemsFromUnimportedNamespaces ?? TypeImportCompletion; } + + /// + /// Whether items from new snippet experience should be included in the completion list. + /// This takes into consideration the experiment we are running in addition to the value + /// from user facing options. + /// + public bool ShouldShowNewSnippetExperience() + { + // Don't trigger snippet completion if the option value is "default" and the experiment is disabled for the user. + return ShowNewSnippetExperience ?? SnippetCompletion; + } } } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractObjectInitializerCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractObjectInitializerCompletionProvider.cs index a85146f816dc9..bca75ce1152c0 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractObjectInitializerCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractObjectInitializerCompletionProvider.cs @@ -62,17 +62,32 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var alreadyTypedMembers = GetInitializedMembers(semanticModel.SyntaxTree, position, cancellationToken); var uninitializedMembers = members.Where(m => !alreadyTypedMembers.Contains(m.Name)); - uninitializedMembers = uninitializedMembers.Where(m => m.IsEditorBrowsable(context.CompletionOptions.HideAdvancedMembers, semanticModel.Compilation)); + // Sort the members by name so if we preselect one, it'll be stable + uninitializedMembers = uninitializedMembers.Where(m => m.IsEditorBrowsable(context.CompletionOptions.HideAdvancedMembers, semanticModel.Compilation)) + .OrderBy(m => m.Name); + + var firstUnitializedRequiredMember = true; foreach (var uninitializedMember in uninitializedMembers) { + var rules = s_rules; + + // We'll hard select the first required member to make it a bit easier to type out an object initializer + // with a bunch of members. + if (firstUnitializedRequiredMember && uninitializedMember.IsRequired()) + { + rules = rules.WithSelectionBehavior(CompletionItemSelectionBehavior.HardSelection).WithMatchPriority(MatchPriority.Preselect); + firstUnitializedRequiredMember = false; + } + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( displayText: EscapeIdentifier(uninitializedMember), displayTextSuffix: "", insertionText: null, symbols: ImmutableArray.Create(uninitializedMember), contextPosition: initializerLocation.SourceSpan.Start, - rules: s_rules)); + inlineDescription: uninitializedMember.IsRequired() ? FeaturesResources.Required : null, + rules: rules)); } } diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs new file mode 100644 index 0000000000000..e571f7b33504a --- /dev/null +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -0,0 +1,123 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ConvertToInterpolatedString; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Completion.Providers.Snippets +{ + internal abstract class AbstractSnippetCompletionProvider : CompletionProvider + { + public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) + { + // This retrieves the document without the text used to invoke completion + // as well as the new cursor position after that has been removed. + var (strippedDocument, position) = await GetDocumentWithoutInvokingTextAsync(document, SnippetCompletionItem.GetInvocationPosition(item), cancellationToken).ConfigureAwait(false); + var service = strippedDocument.GetRequiredLanguageService(); + var snippetIdentifier = SnippetCompletionItem.GetSnippetIdentifier(item); + var snippetProvider = service.GetSnippetProvider(snippetIdentifier); + + // This retrieves the generated Snippet + var snippet = await snippetProvider.GetSnippetAsync(strippedDocument, position, cancellationToken).ConfigureAwait(false); + var strippedText = await strippedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // This introduces the text changes of the snippet into the document with the completion invoking text + var allChangesText = strippedText.WithChanges(snippet.TextChanges); + + // This retrieves ALL text changes from the original document which includes the TextChanges from the snippet + // as well as the clean up. + var allChangesDocument = document.WithText(allChangesText); + var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + + var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); + + // Converts the snippet to an LSP formatted snippet string. + var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change, item.Span.Start, cancellationToken).ConfigureAwait(false); + + // If the TextChanges retrieved starts after the trigger point of the CompletionItem, + // then we need to move the bounds backwards and encapsulate the trigger point. + if (change.Span.Start > item.Span.Start) + { + var textSpan = TextSpan.FromBounds(item.Span.Start, change.Span.End); + var snippetText = change.NewText; + Contract.ThrowIfNull(snippetText); + change = new TextChange(textSpan, snippetText); + } + + var props = ImmutableDictionary.Empty + .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet); + + return CompletionChange.Create(change, allTextChanges.AsImmutable(), properties: props, snippet.CursorPosition, includesCommitCharacter: true); + } + + public override async Task ProvideCompletionsAsync(CompletionContext context) + { + if (!context.CompletionOptions.ShouldShowNewSnippetExperience()) + { + return; + } + + var document = context.Document; + var cancellationToken = context.CancellationToken; + var position = context.Position; + var service = document.GetLanguageService(); + + if (service == null) + { + return; + } + + var (strippedDocument, newPosition) = await GetDocumentWithoutInvokingTextAsync(document, position, cancellationToken).ConfigureAwait(false); + + var snippets = await service.GetSnippetsAsync(strippedDocument, newPosition, cancellationToken).ConfigureAwait(false); + + foreach (var snippetData in snippets) + { + var completionItem = SnippetCompletionItem.Create( + displayText: snippetData.DisplayName, + displayTextSuffix: "", + position: position, + snippetIdentifier: snippetData.SnippetIdentifier, + glyph: Glyph.Snippet); + context.AddItem(completionItem); + } + } + + /// Gets the document without whatever text was used to invoke the completion. + /// Also gets the new position the cursor will be on. + /// Returns the original document and position if completion was invoked using Ctrl-Space. + /// + /// public void Method() + /// { + /// $$ //invoked by typing Ctrl-Space + /// } + /// Example invoking when span is not empty: + /// public void Method() + /// { + /// Wr$$ //invoked by typing out the completion + /// } + private static async Task<(Document, int)> GetDocumentWithoutInvokingTextAsync(Document document, int position, CancellationToken cancellationToken) + { + var originalText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // Uses the existing CompletionService logic to find the TextSpan we want to use for the document sans invoking text + var completionService = document.GetRequiredLanguageService(); + var span = completionService.GetDefaultCompletionListSpan(originalText, position); + + var textChange = new TextChange(span, string.Empty); + originalText = originalText.WithChanges(textChange); + var newDocument = document.WithText(originalText); + return (newDocument, span.Start); + } + } +} diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs new file mode 100644 index 0000000000000..1ef95d39a95f6 --- /dev/null +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs @@ -0,0 +1,54 @@ +// 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 Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Completion.Providers.Snippets +{ + internal class SnippetCompletionItem + { + public static string LSPSnippetKey = "LSPSnippet"; + public static string SnippetIdentifierKey = "SnippetIdentifier"; + + public static CompletionItem Create( + string displayText, + string displayTextSuffix, + int position, + string snippetIdentifier, + Glyph glyph) + { + var props = ImmutableDictionary.Empty + .Add("Position", position.ToString()) + .Add(SnippetIdentifierKey, snippetIdentifier); + + return CommonCompletionItem.Create( + displayText: displayText, + displayTextSuffix: displayTextSuffix, + glyph: glyph, + properties: props, + isComplexTextEdit: true, + rules: CompletionItemRules.Default); + } + + public static string GetSnippetIdentifier(CompletionItem item) + { + Contract.ThrowIfFalse(item.Properties.TryGetValue(SnippetIdentifierKey, out var text)); + return text; + } + + public static int GetInvocationPosition(CompletionItem item) + { + Contract.ThrowIfFalse(item.Properties.TryGetValue("Position", out var text)); + Contract.ThrowIfFalse(int.TryParse(text, out var num)); + return num; + } + + public static bool IsSnippet(CompletionItem item) + { + return item.Properties.TryGetValue(SnippetIdentifierKey, out var _); + } + } +} diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index ec30f46ee82d7..9ccf654f90248 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers { - internal static partial class SymbolCompletionItem + internal static class SymbolCompletionItem { private const string InsertionTextProperty = "InsertionText"; diff --git a/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs index 7b5b738955b4b..99e7af10f0094 100644 --- a/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs @@ -325,10 +325,7 @@ private static void GetInterfaceInfo( return; } - if (explicitInterface == null) - { - explicitInterface = current; - } + explicitInterface ??= current; } // okay, we don't have implicitly implemented one, but we do have explicitly implemented one diff --git a/src/Features/Core/Portable/DocumentationComments/CodeFixes/AbstractAddDocCommentNodesCodeFixProvider.cs b/src/Features/Core/Portable/DocumentationComments/CodeFixes/AbstractAddDocCommentNodesCodeFixProvider.cs index 14dbe2b659d2c..7b3008e0faef4 100644 --- a/src/Features/Core/Portable/DocumentationComments/CodeFixes/AbstractAddDocCommentNodesCodeFixProvider.cs +++ b/src/Features/Core/Portable/DocumentationComments/CodeFixes/AbstractAddDocCommentNodesCodeFixProvider.cs @@ -89,10 +89,7 @@ protected async Task AddParamTagAsync( } // This will be hit in the index is `0`, in which case the previous node is the summary node - if (nodeBeforeNewParamNode == null) - { - nodeBeforeNewParamNode = summaryNode; - } + nodeBeforeNewParamNode ??= summaryNode; newDocComment = newDocComment.InsertNodesAfter(nodeBeforeNewParamNode, new[] { GetNewNode(parameterName, isFirstNodeInComment: false) }); diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 57d735c4ab410..cf13190926053 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -495,11 +495,15 @@ public async Task AnalyzeDocumentAsync( AsyncLazy lazyCapabilities, CancellationToken cancellationToken) { - DocumentAnalysisResults.Log.Write("Analyzing document {0}", newDocument.Name); + var filePath = newDocument.FilePath; + Debug.Assert(newDocument.State.SupportsEditAndContinue()); Debug.Assert(!newActiveStatementSpans.IsDefault); Debug.Assert(newDocument.SupportsSyntaxTree); Debug.Assert(newDocument.SupportsSemanticModel); + Debug.Assert(filePath != null); + + DocumentAnalysisResults.Log.Write("Analyzing document '{0}'", filePath); // assume changes until we determine there are none so that EnC is blocked on unexpected exception: var hasChanges = true; @@ -515,9 +519,7 @@ public async Task AnalyzeDocumentAsync( var oldDocument = await oldProject.GetDocumentAsync(newDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); if (oldDocument != null) { - oldTree = await oldDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(oldTree); - + oldTree = await oldDocument.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); oldRoot = await oldTree.GetRootAsync(cancellationToken).ConfigureAwait(false); oldText = await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); } @@ -549,8 +551,8 @@ public async Task AnalyzeDocumentAsync( { // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways). // If we needed to do so for some reason, we'd need to harden the syntax tree comparers. - DocumentAnalysisResults.Log.Write("{0}: syntax errors", newDocument.Name); - return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Empty, syntaxError, hasChanges); + DocumentAnalysisResults.Log.Write("{0}: syntax errors", filePath); + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Empty, syntaxError, hasChanges); } if (!hasChanges) @@ -560,17 +562,17 @@ public async Task AnalyzeDocumentAsync( // a) comparing texts is cheaper than diffing trees // b) we need to ignore errors in unchanged documents - DocumentAnalysisResults.Log.Write("{0}: unchanged", newDocument.Name); - return DocumentAnalysisResults.Unchanged(newDocument.Id); + DocumentAnalysisResults.Log.Write("{0}: unchanged", filePath); + return DocumentAnalysisResults.Unchanged(newDocument.Id, filePath); } // Disallow modification of a file with experimental features enabled. // These features may not be handled well by the analysis below. if (ExperimentalFeaturesEnabled(newTree)) { - DocumentAnalysisResults.Log.Write("{0}: experimental features enabled", newDocument.Name); + DocumentAnalysisResults.Log.Write("{0}: experimental features enabled", filePath); - return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Create( + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default)), syntaxError: null, hasChanges); } @@ -580,7 +582,7 @@ public async Task AnalyzeDocumentAsync( // If the document has changed at all, lets make sure Edit and Continue is supported if (!capabilities.Grant(EditAndContinueCapabilities.Baseline)) { - return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Create( + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.NotSupportedByRuntime, default)), syntaxError: null, hasChanges); } @@ -606,7 +608,7 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0 && !hasRudeEdits) { - DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, newDocument.FilePath); + DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, filePath); hasRudeEdits = true; } @@ -657,12 +659,13 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0 && !hasRudeEdits) { - DocumentAnalysisResults.Log.Write("{0}@{1}: rude edit ({2} total)", newDocument.FilePath, diagnostics.First().Span.Start, diagnostics.Count); + DocumentAnalysisResults.Log.Write("{0}@{1}: rude edit ({2} total)", filePath, diagnostics.First().Span.Start, diagnostics.Count); hasRudeEdits = true; } return new DocumentAnalysisResults( newDocument.Id, + filePath, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable(), syntaxError: null, @@ -684,7 +687,7 @@ public async Task AnalyzeDocumentAsync( new RudeEditDiagnostic(RudeEditKind.InternalError, span: default, arguments: new[] { newDocument.FilePath, e.ToString() }); // Report as "syntax error" - we can't analyze the document - return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Create(diagnostic), syntaxError: null, hasChanges); + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Create(diagnostic), syntaxError: null, hasChanges); } } @@ -4534,11 +4537,8 @@ private void AddConstructorEdits( private bool HasMemberInitializerContainingLambda(INamedTypeSymbol type, bool isStatic, ref bool? lazyHasMemberInitializerContainingLambda, CancellationToken cancellationToken) { - if (lazyHasMemberInitializerContainingLambda == null) - { - // checking the old type for existing lambdas (it's ok for the new initializers to contain lambdas) - lazyHasMemberInitializerContainingLambda = HasMemberInitializerContainingLambda(type, isStatic, cancellationToken); - } + // checking the old type for existing lambdas (it's ok for the new initializers to contain lambdas) + lazyHasMemberInitializerContainingLambda ??= HasMemberInitializerContainingLambda(type, isStatic, cancellationToken); return lazyHasMemberInitializerContainingLambda.Value; } diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs index 81951a44c0655..b5f103b170012 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs @@ -188,6 +188,7 @@ void AddStatement(LinePositionSpan unmappedLineSpan, ActiveStatement activeState // Also guard against active statements unmapped to multiple locations in the unmapped file // (when multiple #line map to the same span that overlaps with the active statement). if (TryGetTextSpan(oldText.Lines, unmappedLineSpan, out var unmappedSpan) && + oldRoot.FullSpan.Contains(unmappedSpan.Start) && mappedStatements.Add(activeStatement)) { var exceptionRegions = analyzer.GetExceptionRegions(oldRoot, unmappedSpan, activeStatement.IsNonLeaf, cancellationToken); diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index 1f80311fa17b0..195f8b015b4b4 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -615,9 +615,9 @@ public async ValueTask>> GetB var documentId = documentIds[i]; var document = await solution.GetTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); - if (document?.FilePath == null) + if (document?.State.SupportsEditAndContinue() != true) { - // document has been deleted or has no path (can't have an active statement anymore): + // document has been deleted or doesn't support EnC (can't have an active statement anymore): continue; } @@ -627,6 +627,8 @@ public async ValueTask>> GetB continue; } + Contract.ThrowIfNull(document.FilePath); + // Multiple documents may have the same path (linked file). // The documents represent the files that #line directives map to. // Documents that have the same path must have different project id. diff --git a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs index a0e8aefa458e5..92c083c341bc6 100644 --- a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs @@ -20,6 +20,11 @@ internal sealed class DocumentAnalysisResults /// public DocumentId DocumentId { get; } + /// + /// Document file path for logging. + /// + public string FilePath; + /// /// Spans of active statements in the document, or null if the document has syntax errors or has not changed. /// Calculated even in presence of rude edits so that the active statements can be rendered in the editor. @@ -93,6 +98,7 @@ internal sealed class DocumentAnalysisResults public DocumentAnalysisResults( DocumentId documentId, + string filePath, ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits, Diagnostic? syntaxError, @@ -145,6 +151,7 @@ public DocumentAnalysisResults( } DocumentId = documentId; + FilePath = filePath; RudeEditErrors = rudeEdits; SyntaxError = syntaxError; SemanticEdits = semanticEditsOpt; @@ -168,9 +175,10 @@ public bool HasSignificantValidChanges /// /// Report errors blocking the document analysis. /// - public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, ImmutableArray rudeEdits, Diagnostic? syntaxError, bool hasChanges) + public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, string filePath, ImmutableArray rudeEdits, Diagnostic? syntaxError, bool hasChanges) => new( documentId, + filePath, activeStatementsOpt: default, rudeEdits, syntaxError, @@ -184,9 +192,10 @@ public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, Immuta /// /// Report unchanged document results. /// - public static DocumentAnalysisResults Unchanged(DocumentId documentId) + public static DocumentAnalysisResults Unchanged(DocumentId documentId, string filePath) => new( documentId, + filePath, activeStatementsOpt: default, rudeEdits: ImmutableArray.Empty, syntaxError: null, diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 6c77a7e1dda9d..3cbdeb1f6732e 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -380,29 +380,10 @@ private static async ValueTask HasChangedOrAddedDocumentsAsync(Project old HasChangesThatMayAffectSourceGenerators(oldProject.State, newProject.State); } - private static async Task PopulateChangedAndAddedDocumentsAsync(CommittedSolution oldSolution, Project newProject, ArrayBuilder changedOrAddedDocuments, CancellationToken cancellationToken) + private static async Task PopulateChangedAndAddedDocumentsAsync(Project oldProject, Project newProject, ArrayBuilder changedOrAddedDocuments, CancellationToken cancellationToken) { changedOrAddedDocuments.Clear(); - var oldProject = oldSolution.GetProject(newProject.Id); - if (oldProject == null) - { - EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: project not loaded", newProject.Id.DebugName, newProject.Id); - - // TODO (https://github.com/dotnet/roslyn/issues/1204): - // - // When debugging session is started some projects might not have been loaded to the workspace yet (may be explicitly unloaded by the user). - // We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied - // and will result in source mismatch when the user steps into them. - // - // We can allow project to be added by including all its documents here. - // When we analyze these documents later on we'll check if they match the PDB. - // If so we can add them to the committed solution and detect further changes. - // It might be more efficient though to track added projects separately. - - return; - } - if (!await HasChangedOrAddedDocumentsAsync(oldProject, newProject, changedOrAddedDocuments, cancellationToken).ConfigureAwait(false)) { return; @@ -544,7 +525,7 @@ internal void TrackDocumentWithReportedDiagnostics(DocumentId documentId) } } - private static ProjectAnalysisSummary GetProjectAnalysisSymmary(ImmutableArray documentAnalyses) + private static ProjectAnalysisSummary GetProjectAnalysisSummary(ImmutableArray documentAnalyses) { var hasChanges = false; var hasSignificantValidChanges = false; @@ -782,6 +763,8 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution { try { + EditAndContinueWorkspaceService.Log.Write("EmitSolutionUpdate: '{0}'", solution.FilePath); + using var _1 = ArrayBuilder.GetInstance(out var deltas); using var _2 = ArrayBuilder<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)>)>.GetInstance(out var nonRemappableRegions); using var _3 = ArrayBuilder<(ProjectId, EmitBaseline)>.GetInstance(out var emitBaselines); @@ -796,12 +779,33 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution var hasEmitErrors = false; foreach (var newProject in solution.Projects) { - await PopulateChangedAndAddedDocumentsAsync(oldSolution, newProject, changedOrAddedDocuments, cancellationToken).ConfigureAwait(false); + var oldProject = oldSolution.GetProject(newProject.Id); + if (oldProject == null) + { + EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' queried: project not loaded", newProject.FilePath); + + // TODO (https://github.com/dotnet/roslyn/issues/1204): + // + // When debugging session is started some projects might not have been loaded to the workspace yet (may be explicitly unloaded by the user). + // We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied + // and will result in source mismatch when the user steps into them. + // + // We can allow project to be added by including all its documents here. + // When we analyze these documents later on we'll check if they match the PDB. + // If so we can add them to the committed solution and detect further changes. + // It might be more efficient though to track added projects separately. + + continue; + } + + await PopulateChangedAndAddedDocumentsAsync(oldProject, newProject, changedOrAddedDocuments, cancellationToken).ConfigureAwait(false); if (changedOrAddedDocuments.IsEmpty()) { continue; } + EditAndContinueWorkspaceService.Log.Write("Found {0} potentially changed document(s) in project '{1}'", changedOrAddedDocuments.Count, newProject.FilePath); + var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(newProject, cancellationToken).ConfigureAwait(false); if (mvidReadError != null) { @@ -817,7 +821,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution if (mvid == Guid.Empty) { - EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}' [0x{1:X8}]: project not built", newProject.Id.DebugName, newProject.Id); + EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}': project not built", newProject.FilePath); continue; } @@ -846,15 +850,20 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution diagnostics.Add((newProject.Id, documentDiagnostics)); } - var projectSummary = GetProjectAnalysisSymmary(changedDocumentAnalyses); + var projectSummary = GetProjectAnalysisSummary(changedDocumentAnalyses); + EditAndContinueWorkspaceService.Log.Write("Project summary for '{0}': {1}", projectSummary, newProject.FilePath); if (projectSummary == ProjectAnalysisSummary.NoChanges) { continue; } - // PopulateChangedAndAddedDocumentsAsync returns no changes if base project does not exist - var oldProject = oldSolution.GetProject(newProject.Id); - Contract.ThrowIfNull(oldProject); + foreach (var changedDocumentAnalysis in changedDocumentAnalyses) + { + if (changedDocumentAnalysis.HasChanges) + { + EditAndContinueWorkspaceService.Log.Write("Document changed, added, or deleted: '{0}'", changedDocumentAnalysis.FilePath); + } + } // The capability of a module to apply edits may change during edit session if the user attaches debugger to // an additional process that doesn't support EnC (or detaches from such process). Before we apply edits @@ -906,7 +915,7 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution continue; } - EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}' [0x{1:X8}]", newProject.Id.DebugName, newProject.Id); + EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}'", newProject.FilePath); var oldCompilation = await oldProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var newCompilation = await newProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/EditAndContinue/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Extensions.cs index 8bce6176d1ed0..d73513e23d042 100644 --- a/src/Features/Core/Portable/EditAndContinue/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Extensions.cs @@ -75,6 +75,6 @@ public static bool SupportsEditAndContinue(this Project project) public static bool SupportsEditAndContinue(this TextDocumentState documentState) => !documentState.Attributes.DesignTimeOnly && documentState is not DocumentState or DocumentState { SupportsSyntaxTree: true } && - (PathUtilities.IsAbsolute(documentState.FilePath) || documentState is SourceGeneratedDocumentState); + (PathUtilities.IsAbsolute(documentState.FilePath) || documentState is SourceGeneratedDocumentState { FilePath: not null }); } } diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptCommentSlectionServiceImplementation.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptCommentSlectionServiceImplementation.cs index c87538e6e1721..8bfc75bf0de46 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptCommentSlectionServiceImplementation.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptCommentSlectionServiceImplementation.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.Immutable; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { + [Obsolete] internal interface IVSTypeScriptCommentSelectionServiceImplementation : ILanguageService { Task GetInfoAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptCommentSelectionInfo.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptCommentSelectionInfo.cs index f68ef72abc08b..8d08f300f6b71 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptCommentSelectionInfo.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptCommentSelectionInfo.cs @@ -2,10 +2,12 @@ // 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.CommentSelection; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { + [Obsolete] internal readonly struct VSTypeScriptCommentSelectionInfo { internal readonly CommentSelectionInfo UnderlyingObject; diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs index 86c991686eb9e..59bf12fb8e7bd 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs @@ -19,32 +19,18 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript [ExportLanguageService(typeof(ICommentSelectionService), InternalLanguageNames.TypeScript), Shared] internal sealed class VSTypeScriptCommentSelectionService : ICommentSelectionService { - private readonly IVSTypeScriptCommentSelectionServiceImplementation? _impl; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VSTypeScriptCommentSelectionService( - // Optional to work around test issue: https://github.com/dotnet/roslyn/issues/60690 - [Import(AllowDefault = true)] IVSTypeScriptCommentSelectionServiceImplementation? impl) - { - _impl = impl; - } - - public async Task GetInfoAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + public VSTypeScriptCommentSelectionService() { - // Will never be null in product. - Contract.ThrowIfNull(_impl); - - var info = await _impl.GetInfoAsync(document, textSpan, cancellationToken).ConfigureAwait(false); - return info.UnderlyingObject; } - public Task FormatAsync(Document document, ImmutableArray changes, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) - { - // Will never be null in product. - Contract.ThrowIfNull(_impl); - - return _impl.FormatAsync(document, changes, cancellationToken); - } + public CommentSelectionInfo GetInfo() + => new( + supportsSingleLineComment: true, + supportsBlockComment: true, + singleLineCommentString: "//", + blockCommentStartString: "/*", + blockCommentEndString: "*/"); } } diff --git a/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs b/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs index 6cded1b24ebad..079293424ff0e 100644 --- a/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs +++ b/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs @@ -3,12 +3,15 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PullMemberUp; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExtractClass @@ -22,7 +25,7 @@ public AbstractExtractClassRefactoringProvider(IExtractClassOptionsService? serv _optionsService = service; } - protected abstract Task GetSelectedNodeAsync(CodeRefactoringContext context); + protected abstract Task> GetSelectedNodesAsync(CodeRefactoringContext context); protected abstract Task GetSelectedClassDeclarationAsync(CodeRefactoringContext context); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) @@ -55,28 +58,36 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte private async Task TryGetMemberActionAsync(CodeRefactoringContext context, IExtractClassOptionsService optionsService) { - var selectedMemberNode = await GetSelectedNodeAsync(context).ConfigureAwait(false); - if (selectedMemberNode is null) + var selectedMemberNodes = await GetSelectedNodesAsync(context).ConfigureAwait(false); + if (selectedMemberNodes.IsEmpty) { return null; } var (document, span, cancellationToken) = context; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var selectedMember = semanticModel.GetDeclaredSymbol(selectedMemberNode, cancellationToken); - if (selectedMember is null || selectedMember.ContainingType is null) - { - return null; - } + var memberNodeSymbolPairs = selectedMemberNodes + .SelectAsArray(m => (node: m, symbol: semanticModel.GetRequiredDeclaredSymbol(m, cancellationToken))) + // Use same logic as pull members up for determining if a selected member + // is valid to be moved into a base + .WhereAsArray(pair => MemberAndDestinationValidator.IsMemberValid(pair.symbol)); - // Use same logic as pull members up for determining if a selected member - // is valid to be moved into a base - if (!MemberAndDestinationValidator.IsMemberValid(selectedMember)) + if (memberNodeSymbolPairs.IsEmpty) { return null; } - var containingType = selectedMember.ContainingType; + var selectedMembers = memberNodeSymbolPairs.SelectAsArray(pair => pair.symbol); + + var containingType = selectedMembers.First().ContainingType; + Contract.ThrowIfNull(containingType); + + // Treat the entire nodes' span as the span of interest here. That way if the user's location is closer to + // a refactoring with a narrower span (for example, a span just on the name/parameters of a member, then it + // will take precedence over us). + var memberSpan = TextSpan.FromBounds( + memberNodeSymbolPairs.First().node.FullSpan.Start, + memberNodeSymbolPairs.Last().node.FullSpan.End); // Can't extract to a new type if there's already a base. Maybe // in the future we could inject a new type inbetween base and @@ -87,14 +98,15 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte } var syntaxFacts = document.GetRequiredLanguageService(); - var containingTypeDeclarationNode = selectedMemberNode.FirstAncestorOrSelf(syntaxFacts.IsTypeDeclaration); + var containingTypeDeclarationNode = selectedMemberNodes.First().FirstAncestorOrSelf(syntaxFacts.IsTypeDeclaration); Contract.ThrowIfNull(containingTypeDeclarationNode); + if (selectedMemberNodes.Any(m => m.FirstAncestorOrSelf(syntaxFacts.IsTypeDeclaration) != containingTypeDeclarationNode)) + { + return null; + } - // Treat the entire node's span as the span of interest here. That way if the user's location is closer to - // a refactoring with a narrower span (for example, a span just on the name/parameters of a member, then it - // will take precedence over us). return new ExtractClassWithDialogCodeAction( - document, selectedMemberNode.Span, optionsService, containingType, containingTypeDeclarationNode, context.Options, selectedMember); + document, memberSpan, optionsService, containingType, containingTypeDeclarationNode, context.Options, selectedMembers); } private async Task TryGetClassActionAsync(CodeRefactoringContext context, IExtractClassOptionsService optionsService) @@ -110,7 +122,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return null; return new ExtractClassWithDialogCodeAction( - document, span, optionsService, originalType, selectedClassNode, context.Options, selectedMember: null); + document, span, optionsService, originalType, selectedClassNode, context.Options, selectedMembers: ImmutableArray.Empty); } } } diff --git a/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs b/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs index a273f6a2db893..6dda883b378a4 100644 --- a/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs @@ -25,7 +25,7 @@ namespace Microsoft.CodeAnalysis.ExtractClass internal class ExtractClassWithDialogCodeAction : CodeActionWithOptions { private readonly Document _document; - private readonly ISymbol? _selectedMember; + private readonly ImmutableArray _selectedMembers; private readonly INamedTypeSymbol _selectedType; private readonly SyntaxNode _selectedTypeDeclarationNode; private readonly CleanCodeGenerationOptionsProvider _fallbackOptions; @@ -43,26 +43,26 @@ public ExtractClassWithDialogCodeAction( INamedTypeSymbol selectedType, SyntaxNode selectedTypeDeclarationNode, CleanCodeGenerationOptionsProvider fallbackOptions, - ISymbol? selectedMember) + ImmutableArray selectedMembers) { _document = document; _service = service; _selectedType = selectedType; _selectedTypeDeclarationNode = selectedTypeDeclarationNode; _fallbackOptions = fallbackOptions; - _selectedMember = selectedMember; + _selectedMembers = selectedMembers; Span = span; // If the user brought up the lightbulb on a class itself, it's more likely that they want to extract a base // class. on a member however, we deprioritize this as there are likely more member-specific operations // they'd prefer to invoke instead. - Priority = selectedMember is null ? CodeActionPriority.Medium : CodeActionPriority.Low; + Priority = selectedMembers.IsEmpty ? CodeActionPriority.Medium : CodeActionPriority.Low; } public override object? GetOptions(CancellationToken cancellationToken) { var extractClassService = _service ?? _document.Project.Solution.Workspace.Services.GetRequiredService(); - return extractClassService.GetExtractClassOptionsAsync(_document, _selectedType, _selectedMember, cancellationToken) + return extractClassService.GetExtractClassOptionsAsync(_document, _selectedType, _selectedMembers, cancellationToken) .WaitAndGetResult_CanCallOnBackground(cancellationToken); } diff --git a/src/Features/Core/Portable/ExtractClass/IExtractClassOptionsService.cs b/src/Features/Core/Portable/ExtractClass/IExtractClassOptionsService.cs index 3a8fb90d91357..c73db99b74e23 100644 --- a/src/Features/Core/Portable/ExtractClass/IExtractClassOptionsService.cs +++ b/src/Features/Core/Portable/ExtractClass/IExtractClassOptionsService.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.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -10,6 +11,6 @@ namespace Microsoft.CodeAnalysis.ExtractClass { internal interface IExtractClassOptionsService : IWorkspaceService { - Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ISymbol? selectedMember, CancellationToken cancellationToken); + Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ImmutableArray selectedMembers, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 5589332b3fda7..98f8aff7c83e6 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2869,6 +2869,9 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Silent + + Write to the console + embedded Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB @@ -3132,6 +3135,9 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort Imports or usings + + Insert an 'if' statement + Directives from '{0}' @@ -3147,4 +3153,14 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Fixing '{0}' + + Pull selected members up to {0} + + + Pull selected members up + + + required + Used in the object initializer completion. + \ No newline at end of file diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs index ca4ee202ec3b1..db5613273498d 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs @@ -163,7 +163,7 @@ private async ValueTask FixTypeAsync( private IDictionary GetTypeArgumentToTypeParameterMap( CancellationToken cancellationToken) { - return _typeArgumentToTypeParameterMap ?? (_typeArgumentToTypeParameterMap = CreateTypeArgumentToTypeParameterMap(cancellationToken)); + return _typeArgumentToTypeParameterMap ??= CreateTypeArgumentToTypeParameterMap(cancellationToken); } private IDictionary CreateTypeArgumentToTypeParameterMap( diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs index 8439fc7c3bd27..cb8972561e3ef 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs @@ -144,6 +144,8 @@ public ImmutableArray ToMinimalDisplayParts(SemanticModel sem public bool IsNativeIntegerType => _symbol.IsNativeIntegerType; + public bool IsFile => _symbol.IsFile; + public INamedTypeSymbol NativeIntegerUnderlyingType => _symbol.NativeIntegerUnderlyingType; NullableAnnotation ITypeSymbol.NullableAnnotation => throw new NotImplementedException(); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index b07e6a41d964d..46a1f0bbd464a 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -167,10 +167,7 @@ public bool ShouldCollapseOnOpen(string? filePath, BlockStructureOptions blockSt private void InitializeWorkspace(Project project) { - if (_workspace == null) - { - _workspace = new MetadataAsSourceWorkspace(this, project.Solution.Workspace.Services.HostServices); - } + _workspace ??= new MetadataAsSourceWorkspace(this, project.Solution.Workspace.Services.HostServices); } internal async Task MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/MoveStaticMembers/AbstractMoveStaticMembersRefactoringProvider.cs b/src/Features/Core/Portable/MoveStaticMembers/AbstractMoveStaticMembersRefactoringProvider.cs index 82123c7a12c82..7d79b733ffd3a 100644 --- a/src/Features/Core/Portable/MoveStaticMembers/AbstractMoveStaticMembersRefactoringProvider.cs +++ b/src/Features/Core/Portable/MoveStaticMembers/AbstractMoveStaticMembersRefactoringProvider.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.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -9,12 +10,14 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PullMemberUp; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.MoveStaticMembers { internal abstract class AbstractMoveStaticMembersRefactoringProvider : CodeRefactoringProvider { - protected abstract Task GetSelectedNodeAsync(CodeRefactoringContext context); + protected abstract Task> GetSelectedNodesAsync(CodeRefactoringContext context); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { @@ -26,8 +29,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - var memberDeclaration = await GetSelectedNodeAsync(context).ConfigureAwait(false); - if (memberDeclaration == null) + var selectedMemberNodes = await GetSelectedNodesAsync(context).ConfigureAwait(false); + if (selectedMemberNodes.IsEmpty) { return; } @@ -38,26 +41,34 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - var selectedType = semanticModel.GetEnclosingNamedType(span.Start, cancellationToken); - if (selectedType == null) + var memberNodeSymbolPairs = selectedMemberNodes + .SelectAsArray(m => (node: m, symbol: semanticModel.GetRequiredDeclaredSymbol(m, cancellationToken))) + // Use same logic as pull members up for determining if a selected member + // is valid to be moved into a base + .WhereAsArray(pair => MemberAndDestinationValidator.IsMemberValid(pair.symbol) && pair.symbol.IsStatic); + + if (memberNodeSymbolPairs.IsEmpty) { return; } - var selectedMembers = selectedType.GetMembers() - .WhereAsArray(m => m.IsStatic && - MemberAndDestinationValidator.IsMemberValid(m) && - m.DeclaringSyntaxReferences.Any(static (sr, memberDeclaration) => memberDeclaration.FullSpan.Contains(sr.Span), memberDeclaration)); - if (selectedMembers.IsEmpty) + var selectedMembers = memberNodeSymbolPairs.SelectAsArray(pair => pair.symbol); + + var containingType = selectedMembers.First().ContainingType; + Contract.ThrowIfNull(containingType); + if (selectedMembers.Any(m => !m.ContainingType.Equals(containingType))) { return; } - var syntaxFacts = document.GetRequiredLanguageService(); + // we want to use a span which covers all the selected viable member nodes, so that more specific nodes have priority + var memberSpan = TextSpan.FromBounds( + memberNodeSymbolPairs.First().node.FullSpan.Start, + memberNodeSymbolPairs.Last().node.FullSpan.End); - var action = new MoveStaticMembersWithDialogCodeAction(document, span, service, selectedType, context.Options, selectedMember: selectedMembers[0]); + var action = new MoveStaticMembersWithDialogCodeAction(document, service, containingType, context.Options, selectedMembers); - context.RegisterRefactoring(action, selectedMembers[0].DeclaringSyntaxReferences[0].Span); + context.RegisterRefactoring(action, memberSpan); } } } diff --git a/src/Features/Core/Portable/MoveStaticMembers/IMoveStaticMembersOptionsService.cs b/src/Features/Core/Portable/MoveStaticMembers/IMoveStaticMembersOptionsService.cs index fa6cee14dfc15..46b4840be3c36 100644 --- a/src/Features/Core/Portable/MoveStaticMembers/IMoveStaticMembersOptionsService.cs +++ b/src/Features/Core/Portable/MoveStaticMembers/IMoveStaticMembersOptionsService.cs @@ -2,12 +2,13 @@ // 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 Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.MoveStaticMembers { internal interface IMoveStaticMembersOptionsService : IWorkspaceService { - MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ISymbol? selectedNodeSymbol); + MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ImmutableArray selectedNodeSymbols); } } diff --git a/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersOptions.cs b/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersOptions.cs index a9d08e971892f..6975629882700 100644 --- a/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersOptions.cs +++ b/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersOptions.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Linq; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.MoveStaticMembers { @@ -13,9 +14,16 @@ internal readonly struct MoveStaticMembersOptions public string FileName { get; } - public string TypeName { get; } + public bool IsNewType { get; } - public string NamespaceDisplay { get; } + // only has value when IsNewType is false + public INamedTypeSymbol? Destination { get; } + + // only has value when IsNewType is true + public string? TypeName { get; } + + // only has value when IsNewType is true + public string? NamespaceDisplay { get; } public ImmutableArray SelectedMembers { get; } @@ -25,6 +33,23 @@ internal readonly struct MoveStaticMembersOptions ImmutableArray.Empty, isCancelled: true); + public MoveStaticMembersOptions( + INamedTypeSymbol destination, + ImmutableArray selectedMembers, + bool isCancelled = false) + { + var sourceLocation = destination.DeclaringSyntaxReferences.First(); + RoslynDebug.AssertNotNull(sourceLocation.SyntaxTree); + + IsCancelled = isCancelled; + FileName = sourceLocation.SyntaxTree.FilePath; + IsNewType = false; + Destination = destination; + TypeName = null; + NamespaceDisplay = null; + SelectedMembers = selectedMembers; + } + public MoveStaticMembersOptions( string fileName, string fullTypeName, @@ -33,6 +58,8 @@ public MoveStaticMembersOptions( { IsCancelled = isCancelled; FileName = fileName; + IsNewType = true; + Destination = null; var namespacesAndType = fullTypeName.Split(separator: '.'); TypeName = namespacesAndType.Last(); NamespaceDisplay = string.Join(separator: ".", namespacesAndType.Take(namespacesAndType.Length - 1)); diff --git a/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs b/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs index f3dc5a851c09e..f2584373ff5f2 100644 --- a/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs @@ -25,33 +25,30 @@ namespace Microsoft.CodeAnalysis.MoveStaticMembers internal class MoveStaticMembersWithDialogCodeAction : CodeActionWithOptions { private readonly Document _document; - private readonly ISymbol? _selectedMember; + private readonly ImmutableArray _selectedMembers; private readonly INamedTypeSymbol _selectedType; private readonly IMoveStaticMembersOptionsService _service; private readonly CleanCodeGenerationOptionsProvider _fallbackOptions; - public TextSpan Span { get; } public override string Title => FeaturesResources.Move_static_members_to_another_type; public MoveStaticMembersWithDialogCodeAction( Document document, - TextSpan span, IMoveStaticMembersOptionsService service, INamedTypeSymbol selectedType, CleanCodeGenerationOptionsProvider fallbackOptions, - ISymbol? selectedMember = null) + ImmutableArray selectedMembers) { _document = document; _service = service; _selectedType = selectedType; _fallbackOptions = fallbackOptions; - _selectedMember = selectedMember; - Span = span; + _selectedMembers = selectedMembers; } public override object? GetOptions(CancellationToken cancellationToken) { - return _service.GetMoveMembersToTypeOptions(_document, _selectedType, _selectedMember); + return _service.GetMoveMembersToTypeOptions(_document, _selectedType, _selectedMembers); } protected override async Task> ComputeOperationsAsync(object options, CancellationToken cancellationToken) @@ -75,6 +72,26 @@ protected override async Task> ComputeOperation root = root.TrackNodes(memberNodes); var sourceDoc = _document.WithSyntaxRoot(root); + if (!moveOptions.IsNewType) + { + // we already have our destination type, but we need to find the document it is in + // When it is an existing type, "FileName" points to a full path rather than just the name + // There should be no two docs that have the same file path + var destinationDocId = _document.Project.Solution.GetDocumentIdsWithFilePath(moveOptions.FileName).Single(); + var fixedSolution = await RefactorAndMoveAsync( + moveOptions.SelectedMembers, + memberNodes, + sourceDoc.Project.Solution, + moveOptions.Destination!, + // TODO: Find a way to merge/change generic type args for classes, or change PullMembersUp to handle instead + typeArgIndices: ImmutableArray.Empty, + sourceDoc.Id, + destinationDocId, + cancellationToken).ConfigureAwait(false); + return new CodeActionOperation[] { new ApplyChangesOperation(fixedSolution) }; + } + + // otherwise, we need to create a destination ourselves var typeParameters = ExtractTypeHelpers.GetRequiredTypeParametersForMembers(_selectedType, moveOptions.SelectedMembers); // which indices of the old type params should we keep for a new class reference, used for refactoring usages var typeArgIndices = Enumerable.Range(0, _selectedType.TypeParameters.Length) @@ -87,7 +104,7 @@ protected override async Task> ComputeOperation Accessibility.NotApplicable, DeclarationModifiers.Static, GetNewTypeKind(_selectedType), - moveOptions.TypeName, + moveOptions.TypeName!, typeParameters: typeParameters); var (newDoc, annotation) = await ExtractTypeHelpers.AddTypeToNewFileAsync( @@ -137,6 +154,60 @@ private static TypeKind GetNewTypeKind(INamedTypeSymbol oldType) return oldType.TypeKind; } + /// + /// Finds references, refactors them, then moves the selected members to the destination. + /// Used when the destination type/file already exists. + /// + /// selected member symbols + /// nodes corresponding to those symbols in the old solution, should have been annotated + /// solution without any members moved/refactored + /// the type to move to, should be inserted into a document already + /// generic type arg indices to keep when refactoring generic class access to the new type. Empty if not relevant + /// Id of the document where the mebers are being moved from + /// The solution with references refactored and members moved to the newType + private async Task RefactorAndMoveAsync( + ImmutableArray selectedMembers, + ImmutableArray oldMemberNodes, + Solution oldSolution, + INamedTypeSymbol newType, + ImmutableArray typeArgIndices, + DocumentId sourceDocId, + DocumentId newTypeDocId, + CancellationToken cancellationToken) + { + // annotate our new type, in case our refactoring changes it + var newTypeDoc = await oldSolution.GetRequiredDocumentAsync(newTypeDocId, cancellationToken: cancellationToken).ConfigureAwait(false); + var newTypeRoot = await newTypeDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newTypeNode = newType.DeclaringSyntaxReferences + .SelectAsArray(sRef => sRef.GetSyntax(cancellationToken)) + .First(node => newTypeRoot.Contains(node)); + newTypeRoot = newTypeRoot.TrackNodes(newTypeNode); + oldSolution = newTypeDoc.WithSyntaxRoot(newTypeRoot).Project.Solution; + + // refactor references across the entire solution + var memberReferenceLocations = await FindMemberReferencesAsync(selectedMembers, oldSolution, cancellationToken).ConfigureAwait(false); + var projectToLocations = memberReferenceLocations.ToLookup(loc => loc.location.Document.Project.Id); + var solutionWithFixedReferences = await RefactorReferencesAsync(projectToLocations, oldSolution, newType, typeArgIndices, cancellationToken).ConfigureAwait(false); + + var sourceDoc = solutionWithFixedReferences.GetRequiredDocument(sourceDocId); + + // get back tracked nodes from our changes + var root = await sourceDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await sourceDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var members = oldMemberNodes + .Select(node => root.GetCurrentNode(node)) + .WhereNotNull() + .SelectAsArray(node => (semanticModel.GetDeclaredSymbol(node, cancellationToken), false)); + + newTypeDoc = solutionWithFixedReferences.GetRequiredDocument(newTypeDoc.Id); + newTypeRoot = await newTypeDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newTypeSemanticModel = await newTypeDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + newType = (INamedTypeSymbol)newTypeSemanticModel.GetRequiredDeclaredSymbol(newTypeRoot.GetCurrentNode(newTypeNode)!, cancellationToken); + + var pullMembersUpOptions = PullMembersUpOptionsBuilder.BuildPullMembersUpOptions(newType, members); + return await MembersPuller.PullMembersUpAsync(sourceDoc, pullMembersUpOptions, _fallbackOptions, cancellationToken).ConfigureAwait(false); + } + private static async Task RefactorReferencesAsync( ILookup projectToLocations, Solution solution, diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs index 94a2974f22c40..dd2b73c4a9a8d 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs @@ -270,11 +270,8 @@ private static async Task MoveTypeToNamespaceAsync( var syntaxRoot = await mergedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxNode = syntaxRoot.GetAnnotatedNodes(AbstractMoveTypeService.NamespaceScopeMovedAnnotation).SingleOrDefault(); - if (syntaxNode == null) - { - // The type might be declared in global namespace - syntaxNode = container.FirstAncestorOrSelf() ?? syntaxRoot; - } + // The type might be declared in global namespace + syntaxNode ??= container.FirstAncestorOrSelf() ?? syntaxRoot; return await MoveItemsInNamespaceAsync( mergedDocument, diff --git a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.cs b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.cs index 19da337832380..79b20a9c41ea6 100644 --- a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.cs +++ b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.cs @@ -24,10 +24,7 @@ public DefaultDocumentNavigationServiceFactory() public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - if (_singleton == null) - { - _singleton = new DefaultDocumentNavigationService(); - } + _singleton ??= new DefaultDocumentNavigationService(); return _singleton; } diff --git a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationServiceFactory.cs b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationServiceFactory.cs index 0cd9b6da0403f..9c329e6a5471d 100644 --- a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationServiceFactory.cs +++ b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationServiceFactory.cs @@ -24,10 +24,7 @@ public DefaultSymbolNavigationServiceFactory() public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - if (_singleton == null) - { - _singleton = new DefaultSymbolNavigationService(); - } + _singleton ??= new DefaultSymbolNavigationService(); return _singleton; } diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index 42cd6f645a81c..172c8ec461bd2 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -10,5 +10,6 @@ *REMOVED*Microsoft.CodeAnalysis.Completion.CompletionServiceWithProviders Microsoft.CodeAnalysis.Completion.CompletionService.GetRules() -> Microsoft.CodeAnalysis.Completion.CompletionRules Microsoft.CodeAnalysis.Completion.CompletionService.ShouldTriggerCompletion(Microsoft.CodeAnalysis.Text.SourceText text, int caretPosition, Microsoft.CodeAnalysis.Completion.CompletionTrigger trigger, System.Collections.Immutable.ImmutableHashSet roles = null, Microsoft.CodeAnalysis.Options.OptionSet options = null) -> bool +Microsoft.CodeAnalysis.Completion.CompletionList.ItemsList.get -> System.Collections.Generic.IReadOnlyList Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task Microsoft.CodeAnalysis.Completion.CompletionService.GetCompletionsAsync(Microsoft.CodeAnalysis.Document document, int caretPosition, Microsoft.CodeAnalysis.Completion.CompletionTrigger trigger = default(Microsoft.CodeAnalysis.Completion.CompletionTrigger), System.Collections.Immutable.ImmutableHashSet roles = null, Microsoft.CodeAnalysis.Options.OptionSet options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs index 39580abc1b7b5..4cf8285a5a078 100644 --- a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs +++ b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeRefactorings.PullMemberUp.Dialog; using Microsoft.CodeAnalysis.PullMemberUp; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using static Microsoft.CodeAnalysis.CodeActions.CodeAction; @@ -19,7 +20,7 @@ internal abstract partial class AbstractPullMemberUpRefactoringProvider : CodeRe { private IPullMemberUpOptionsService? _service; - protected abstract Task GetSelectedNodeAsync(CodeRefactoringContext context); + protected abstract Task> GetSelectedNodesAsync(CodeRefactoringContext context); /// /// Test purpose only @@ -39,26 +40,39 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - var selectedMemberNode = await GetSelectedNodeAsync(context).ConfigureAwait(false); - if (selectedMemberNode == null) + var selectedMemberNodes = await GetSelectedNodesAsync(context).ConfigureAwait(false); + if (selectedMemberNodes.IsEmpty) { return; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var selectedMember = semanticModel.GetDeclaredSymbol(selectedMemberNode); - if (selectedMember == null || selectedMember.ContainingType == null) + var memberNodeSymbolPairs = selectedMemberNodes + .SelectAsArray(m => (node: m, symbol: semanticModel.GetRequiredDeclaredSymbol(m, cancellationToken))) + .WhereAsArray(pair => MemberAndDestinationValidator.IsMemberValid(pair.symbol)); + + if (memberNodeSymbolPairs.IsEmpty) { return; } - if (!MemberAndDestinationValidator.IsMemberValid(selectedMember)) + var selectedMembers = memberNodeSymbolPairs.SelectAsArray(pair => pair.symbol); + + var containingType = selectedMembers.First().ContainingType; + Contract.ThrowIfNull(containingType); + if (selectedMembers.Any(m => !m.ContainingType.Equals(containingType))) { return; } + // we want to use a span which covers all the selected viable member nodes, so that more specific nodes have priority + var memberSpan = TextSpan.FromBounds( + memberNodeSymbolPairs.First().node.FullSpan.Start, + memberNodeSymbolPairs.Last().node.FullSpan.End); + var allDestinations = FindAllValidDestinations( - selectedMember, + selectedMembers, + containingType, document.Project.Solution, cancellationToken); if (allDestinations.Length == 0) @@ -66,23 +80,29 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - var allActions = allDestinations.Select(destination => MembersPuller.TryComputeCodeAction(document, selectedMember, destination, context.Options)) - .WhereNotNull().Concat(new PullMemberUpWithDialogCodeAction(document, selectedMember, _service, context.Options)) + var allActions = allDestinations.Select(destination => MembersPuller.TryComputeCodeAction(document, selectedMembers, destination, context.Options)) + .WhereNotNull() + .Concat(new PullMemberUpWithDialogCodeAction(document, selectedMembers, _service, context.Options)) .ToImmutableArray(); + var title = selectedMembers.IsSingle() + ? string.Format(FeaturesResources.Pull_0_up, selectedMembers.Single().ToNameDisplayString()) + : FeaturesResources.Pull_selected_members_up; + var nestedCodeAction = CodeActionWithNestedActions.Create( - string.Format(FeaturesResources.Pull_0_up, selectedMember.ToNameDisplayString()), + title, allActions, isInlinable: true); - context.RegisterRefactoring(nestedCodeAction, selectedMemberNode.Span); + + context.RegisterRefactoring(nestedCodeAction, memberSpan); } private static ImmutableArray FindAllValidDestinations( - ISymbol selectedMember, + ImmutableArray selectedMembers, + INamedTypeSymbol containingType, Solution solution, CancellationToken cancellationToken) { - var containingType = selectedMember.ContainingType; - var allDestinations = selectedMember.IsKind(SymbolKind.Field) + var allDestinations = selectedMembers.All(m => m.IsKind(SymbolKind.Field)) ? containingType.GetBaseTypes().ToImmutableArray() : containingType.AllInterfaces.Concat(containingType.GetBaseTypes()).ToImmutableArray(); diff --git a/src/Features/Core/Portable/PullMemberUp/Dialog/IPullMemberUpOptionsService.cs b/src/Features/Core/Portable/PullMemberUp/Dialog/IPullMemberUpOptionsService.cs index 8004f331aeec2..5959054f7296a 100644 --- a/src/Features/Core/Portable/PullMemberUp/Dialog/IPullMemberUpOptionsService.cs +++ b/src/Features/Core/Portable/PullMemberUp/Dialog/IPullMemberUpOptionsService.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PullMemberUp; @@ -11,6 +12,6 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings.PullMemberUp.Dialog { internal interface IPullMemberUpOptionsService : IWorkspaceService { - PullMembersUpOptions GetPullMemberUpOptions(Document document, ISymbol selectedNodeSymbol); + PullMembersUpOptions GetPullMemberUpOptions(Document document, ImmutableArray selectedNodeSymbols); } } diff --git a/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs b/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs index 11e8506369501..81c7843ee27ea 100644 --- a/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -20,7 +21,7 @@ private sealed class PullMemberUpWithDialogCodeAction : CodeActionWithOptions /// /// Member which user initially selects. It will be selected initially when the dialog pops up. /// - private readonly ISymbol _selectedMember; + private readonly ImmutableArray _selectedMembers; private readonly Document _document; private readonly IPullMemberUpOptionsService _service; private readonly CleanCodeGenerationOptionsProvider _fallbackOptions; @@ -29,19 +30,19 @@ private sealed class PullMemberUpWithDialogCodeAction : CodeActionWithOptions public PullMemberUpWithDialogCodeAction( Document document, - ISymbol selectedMember, + ImmutableArray selectedMembers, IPullMemberUpOptionsService service, CleanCodeGenerationOptionsProvider fallbackOptions) { _document = document; - _selectedMember = selectedMember; + _selectedMembers = selectedMembers; _service = service; _fallbackOptions = fallbackOptions; } public override object GetOptions(CancellationToken cancellationToken) { - return _service.GetPullMemberUpOptions(_document, _selectedMember); + return _service.GetPullMemberUpOptions(_document, _selectedMembers); } protected override async Task> ComputeOperationsAsync(object options, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs index 6fd304cd19ae3..c2f5718b65365 100644 --- a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs +++ b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs @@ -36,18 +36,23 @@ internal static class MembersPuller public static CodeAction TryComputeCodeAction( Document document, - ISymbol selectedMember, + ImmutableArray selectedMembers, INamedTypeSymbol destination, CleanCodeGenerationOptionsProvider fallbackOptions) { - var result = PullMembersUpOptionsBuilder.BuildPullMembersUpOptions(destination, ImmutableArray.Create((member: selectedMember, makeAbstract: false))); + var result = PullMembersUpOptionsBuilder.BuildPullMembersUpOptions(destination, + selectedMembers.SelectAsArray(m => (member: m, makeAbstract: false))); + if (result.PullUpOperationNeedsToDoExtraChanges || - IsSelectedMemberDeclarationAlreadyInDestination(selectedMember, destination)) + selectedMembers.Any(IsSelectedMemberDeclarationAlreadyInDestination, destination)) { return null; } - var title = string.Format(FeaturesResources.Pull_0_up_to_1, selectedMember.Name, result.Destination.Name); + var title = selectedMembers.IsSingle() + ? string.Format(FeaturesResources.Pull_0_up_to_1, selectedMembers.Single().Name, result.Destination.Name) + : string.Format(FeaturesResources.Pull_selected_members_up_to_0, result.Destination.Name); + return SolutionChangeAction.Create( title, cancellationToken => PullMembersUpAsync(document, result, fallbackOptions, cancellationToken), diff --git a/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs b/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs index c55bbdda570c3..4cee32558ff66 100644 --- a/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs +++ b/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs @@ -53,7 +53,7 @@ private static string RemovePrefixesAndSuffixes(ISymbol symbol, ImmutableArray> _lazySnippetProviders; + private readonly Dictionary _identifierToProviderMap = new(); + private readonly object _snippetProvidersLock = new(); + private ImmutableArray _snippetProviders; + + public AbstractSnippetService(IEnumerable> lazySnippetProviders) + { + _lazySnippetProviders = lazySnippetProviders.ToImmutableArray(); + } + + /// + /// This should never be called prior to GetSnippetsAsync because it gets populated + /// at that point in time. + /// + public ISnippetProvider GetSnippetProvider(string snippetIdentifier) + { + Contract.ThrowIfFalse(_identifierToProviderMap.ContainsKey(snippetIdentifier)); + return _identifierToProviderMap[snippetIdentifier]; + } + + /// + /// Iterates through all providers and determines if the snippet + /// can be added to the Completion list at the corresponding position. + /// + public async Task> GetSnippetsAsync(Document document, int position, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + foreach (var provider in GetSnippetProviders(document)) + { + var snippetData = await provider.GetSnippetDataAsync(document, position, cancellationToken).ConfigureAwait(false); + arrayBuilder.AddIfNotNull(snippetData); + } + + return arrayBuilder.ToImmutable(); + } + + private ImmutableArray GetSnippetProviders(Document document) + { + lock (_snippetProvidersLock) + { + if (_snippetProviders.IsDefault) + { + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + foreach (var provider in _lazySnippetProviders.Where(p => p.Metadata.Language == document.Project.Language)) + { + var providerData = provider.Value; + Debug.Assert(!_identifierToProviderMap.TryGetValue(providerData.SnippetIdentifier, out var _)); + _identifierToProviderMap.Add(providerData.SnippetIdentifier, providerData); + arrayBuilder.Add(providerData); + } + + _snippetProviders = arrayBuilder.ToImmutable(); + } + } + + return _snippetProviders; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.cs b/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.cs new file mode 100644 index 0000000000000..d20fbfefef59b --- /dev/null +++ b/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.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; +using System.Composition; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; + +namespace Microsoft.CodeAnalysis.Snippets +{ + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class)] + internal sealed class ExportSnippetProviderAttribute : ExportAttribute + { + public string Name { get; } + public string Language { get; } + + public ExportSnippetProviderAttribute(string name, string language) + : base(typeof(ISnippetProvider)) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Language = language ?? throw new ArgumentNullException(nameof(language)); + } + } +} diff --git a/src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.cs b/src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.cs new file mode 100644 index 0000000000000..7b0763207e898 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.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.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal interface IRoslynLSPSnippetExpander + { + bool CanExpandSnippet(); + } +} diff --git a/src/Features/Core/Portable/Snippets/ISnippetService.cs b/src/Features/Core/Portable/Snippets/ISnippetService.cs new file mode 100644 index 0000000000000..f005325da09da --- /dev/null +++ b/src/Features/Core/Portable/Snippets/ISnippetService.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 System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal interface ISnippetService : ILanguageService + { + /// + /// Retrieves all possible types of snippets for a particular position + /// + Task> GetSnippetsAsync(Document document, int position, CancellationToken cancellationToken); + + /// + /// Gets the corresponding provider from a snippet identifier. + /// Called upon by the AbstractSnippetCompletionProvider + /// + ISnippetProvider GetSnippetProvider(string snippetIdentifier); + } +} diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs new file mode 100644 index 0000000000000..a57a48e25bc39 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal static class RoslynLSPSnippetConverter + { + /// + /// Extends the TextChange to encompass all placeholder positions as well as caret position. + /// Generates a LSP formatted snippet from a TextChange, list of placeholders, and caret position. + /// + public static async Task GenerateLSPSnippetAsync(Document document, int caretPosition, ImmutableArray placeholders, TextChange textChange, int triggerLocation, CancellationToken cancellationToken) + { + var extendedTextChange = await ExtendSnippetTextChangeAsync(document, textChange, placeholders, caretPosition, triggerLocation, cancellationToken).ConfigureAwait(false); + return ConvertToLSPSnippetString(extendedTextChange, placeholders, caretPosition); + } + + /// + /// Iterates through every index in the snippet string and determines where the + /// LSP formatted chunks should be inserted for each placeholder. + /// + private static string ConvertToLSPSnippetString(TextChange textChange, ImmutableArray placeholders, int caretPosition) + { + var textChangeStart = textChange.Span.Start; + var textChangeText = textChange.NewText; + Contract.ThrowIfNull(textChangeText); + + using var _1 = PooledStringBuilder.GetInstance(out var lspSnippetString); + using var _2 = PooledDictionary.GetInstance(out var dictionary); + PopulateMapOfSpanStartsToLSPStringItem(dictionary, placeholders, textChangeStart); + + // Need to go through the length + 1 since caret postions occur before and after the + // character position. + // If there is a caret at the end of the line, then it's position + // will be equivalent to the length of the TextChange. + for (var i = 0; i < textChange.Span.Length + 1;) + { + if (i == caretPosition - textChangeStart) + { + lspSnippetString.Append("$0"); + } + + //Tries to see if a value exists at that position in the map, and if so it + // generates a string that is LSP formatted. + if (dictionary.TryGetValue(i, out var placeholderInfo)) + { + var str = $"${{{placeholderInfo.priority}:{placeholderInfo.identifier}}}"; + lspSnippetString.Append(str); + + // Skip past the entire identifier in the TextChange text + i += placeholderInfo.identifier.Length; + } + else + { + if (i < textChangeText.Length) + { + lspSnippetString.Append(textChangeText[i]); + i++; + } + else + { + break; + } + } + } + + return lspSnippetString.ToString(); + } + + /// + /// Preprocesses the list of placeholders into a dictionary that maps the insertion position + /// in the string to the placeholder's identifier and the priority associated with it. + /// + private static void PopulateMapOfSpanStartsToLSPStringItem(Dictionary dictionary, ImmutableArray placeholders, int textChangeStart) + { + for (var i = 0; i < placeholders.Length; i++) + { + var placeholder = placeholders[i]; + foreach (var position in placeholder.PlaceHolderPositions) + { + // i + 1 since the placeholder priority is set according to the index in the + // placeholders array, starting at 1. + // We should never be adding two placeholders in the same position since identifiers + // must have a length greater than 0. + dictionary.Add(position - textChangeStart, (placeholder.Identifier, i + 1)); + } + } + } + + /// + /// We need to extend the snippet's TextChange if any of the placeholders or + /// if the caret position comes before or after the span of the TextChange. + /// If so, then find the new string that encompasses all of the placeholders + /// and caret position. + /// This is important for the cases in which the document does not determine the TextChanges from + /// the original document accurately. + /// + private static async Task ExtendSnippetTextChangeAsync(Document document, TextChange textChange, ImmutableArray placeholders, int caretPosition, int triggerLocation, CancellationToken cancellationToken) + { + var extendedSpan = GetUpdatedTextSpan(textChange, placeholders, caretPosition, triggerLocation); + var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newString = documentText.ToString(extendedSpan); + var newTextChange = new TextChange(extendedSpan, newString); + + return newTextChange; + } + + /// + /// Iterates through the placeholders and determines if any of the positions + /// come before or after what is indicated by the snippet's TextChange. + /// If so, adjust the starting and ending position accordingly. + /// + private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray placeholders, int caretPosition, int triggerLocation) + { + var textChangeText = textChange.NewText; + Contract.ThrowIfNull(textChangeText); + + var startPosition = textChange.Span.Start; + var endPosition = textChange.Span.Start + textChangeText.Length; + + if (placeholders.Length > 0) + { + startPosition = Math.Min(startPosition, placeholders.Min(placeholder => placeholder.PlaceHolderPositions.Min())); + endPosition = Math.Max(endPosition, placeholders.Max(placeholder => placeholder.PlaceHolderPositions.Max())); + } + + startPosition = Math.Min(startPosition, caretPosition); + endPosition = Math.Max(endPosition, caretPosition); + + startPosition = Math.Min(startPosition, triggerLocation); + + return TextSpan.FromBounds(startPosition, endPosition); + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs new file mode 100644 index 0000000000000..584d9a7aea2d8 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -0,0 +1,48 @@ +// 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 Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + /// + /// Encapsulates the information that makes up a Snippet. + /// + internal readonly struct SnippetChange + { + /// + /// The TextChange's associated with introducing a snippet into a document + /// + public readonly ImmutableArray TextChanges; + + /// + /// The position that the cursor should end up on + /// + public readonly int CursorPosition; + + /// + /// The items that we will want to rename as well as the ordering + /// in which to visit those items. + /// + public readonly ImmutableArray Placeholders; + + public SnippetChange( + ImmutableArray textChanges, + int cursorPosition, + ImmutableArray placeholders) + { + if (textChanges.IsEmpty) + { + throw new ArgumentException($"{nameof(textChanges)} must not be empty."); + } + + TextChanges = textChanges; + CursorPosition = cursorPosition; + Placeholders = placeholders; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetData.cs b/src/Features/Core/Portable/Snippets/SnippetData.cs new file mode 100644 index 0000000000000..b28be567c6079 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetData.cs @@ -0,0 +1,27 @@ +// 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.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + /// + /// Stores only the data needed for the creation of a CompletionItem. + /// Avoids using the Snippet and creating a TextChange/finding cursor + /// position before we know it was the selected CompletionItem. + /// + internal struct SnippetData + { + public readonly string DisplayName; + public readonly string SnippetIdentifier; + + public SnippetData(string displayName, string snippetIdentifier) + { + DisplayName = displayName; + SnippetIdentifier = snippetIdentifier; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs new file mode 100644 index 0000000000000..5932bdfeff17d --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal readonly struct SnippetPlaceholder + { + /// + /// The identifier in the snippet that needs to be renamed. + /// + public readonly string Identifier; + + /// + /// The positions associated with the identifier that will need to + /// be converted into LSP formatted strings. + /// + public readonly ImmutableArray PlaceHolderPositions; + + /// + /// + /// For loop would have two placeholders: + /// + /// for (var {1:i} = 0; {1:i} < {2:length}; {1:i}++) + /// + /// Identifier: i, 3 associated positions
+ /// Identifier: length, 1 associated position
+ ///
+ ///
+ public SnippetPlaceholder(string identifier, ImmutableArray placeholderPositions) + { + if (identifier.Length == 0) + { + throw new ArgumentException($"{nameof(identifier)} must not be an empty string."); + } + + Identifier = identifier; + PlaceHolderPositions = placeholderPositions; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs new file mode 100644 index 0000000000000..51ca5a533c985 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -0,0 +1,167 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal abstract class AbstractConsoleSnippetProvider : AbstractSnippetProvider + { + protected abstract SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken token); + + public override string SnippetIdentifier => "cw"; + + public override string SnippetDisplayName => FeaturesResources.Write_to_the_console; + + protected override async Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken) + { + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); + var consoleSymbol = await GetSymbolFromMetaDataNameAsync(document, cancellationToken).ConfigureAwait(false); + if (consoleSymbol is null) + { + return false; + } + + var syntaxContext = document.GetRequiredLanguageService().CreateContext(document, semanticModel, position, cancellationToken); + return syntaxContext.IsStatementContext || syntaxContext.IsGlobalStatementContext; + } + + protected override async Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) + { + var snippetTextChange = await GenerateSnippetTextChangeAsync(document, position, cancellationToken).ConfigureAwait(false); + return ImmutableArray.Create(snippetTextChange); + } + + private async Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + { + var consoleSymbol = await GetSymbolFromMetaDataNameAsync(document, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(consoleSymbol); + var generator = SyntaxGenerator.GetGenerator(document); + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); + + // We know symbol is not null at this point since it was checked when determining + // if we are in a valid location to insert the snippet. + var typeExpression = generator.TypeExpression(consoleSymbol); + var declaration = GetAsyncSupportingDeclaration(token); + var isAsync = declaration is not null && generator.GetModifiers(declaration).IsAsync; + + var invocation = isAsync + ? generator.AwaitExpression(generator.InvocationExpression( + generator.MemberAccessExpression(generator.MemberAccessExpression(typeExpression, generator.IdentifierName(nameof(Console.Out))), generator.IdentifierName(nameof(Console.Out.WriteLineAsync))))) + : generator.InvocationExpression(generator.MemberAccessExpression(typeExpression, generator.IdentifierName(nameof(Console.WriteLine)))); + var expressionStatement = generator.ExpressionStatement(invocation); + + // Need to normalize the whitespace for the asynchronous case because it doesn't insert a space following the await + return new TextChange(TextSpan.FromBounds(position, position), expressionStatement.NormalizeWhitespace().ToFullString()); + } + + /// + /// Tries to get the location after the open parentheses in the argument list. + /// If it can't, then we default to the end of the snippet's span. + /// + protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) + { + var invocationExpression = caretTarget.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); + if (invocationExpression is null) + { + return caretTarget.Span.End; + } + + var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); + if (argumentListNode is null) + { + return caretTarget.Span.End; + } + + syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); + return openParenToken.Span.End; + } + + protected override async Task AnnotateNodesToReformatAsync(Document document, + SyntaxAnnotation findSnippetAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position, syntaxFacts); + if (snippetExpressionNode is null) + { + return root; + } + + var consoleSymbol = await GetSymbolFromMetaDataNameAsync(document, cancellationToken).ConfigureAwait(false); + + var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(findSnippetAnnotation, cursorAnnotation, Simplifier.Annotation, SymbolAnnotation.Create(consoleSymbol!), Formatter.Annotation); + return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); + } + + protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + { + return ImmutableArray.Empty; + } + + private static SyntaxToken? GetOpenParenToken(SyntaxNode node, ISyntaxFacts syntaxFacts) + { + var invocationExpression = node.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); + if (invocationExpression is null) + { + return null; + } + + var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); + if (argumentListNode is null) + { + return null; + } + + syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); + + return openParenToken; + } + + protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts) + { + var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); + var nearestExpressionStatement = closestNode.FirstAncestorOrSelf(syntaxFacts.IsExpressionStatement); + if (nearestExpressionStatement is null) + { + return null; + } + + // Checking to see if that expression statement that we found is + // starting at the same position as the position we inserted + // the Console WriteLine expression statement. + if (nearestExpressionStatement.SpanStart != position) + { + return null; + } + + return nearestExpressionStatement; + } + + private static async Task GetSymbolFromMetaDataNameAsync(Document document, CancellationToken cancellationToken) + { + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var symbol = compilation.GetBestTypeByMetadataName(typeof(Console).FullName!); + return symbol; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs new file mode 100644 index 0000000000000..8d9a4e2eb9e6a --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.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.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider + { + public override string SnippetIdentifier => "if"; + + public override string SnippetDisplayName => FeaturesResources.Insert_an_if_statement; + + protected abstract void GetIfStatementConditionAndCursorPosition(SyntaxNode node, out SyntaxNode condition, out int cursorPositionNode); + + protected override async Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken) + { + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); + + var syntaxContext = document.GetRequiredLanguageService().CreateContext(document, semanticModel, position, cancellationToken); + return syntaxContext.IsStatementContext || syntaxContext.IsGlobalStatementContext; + } + + protected override Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) + { + var snippetTextChange = GenerateSnippetTextChange(document, position); + return Task.FromResult(ImmutableArray.Create(snippetTextChange)); + } + + private static TextChange GenerateSnippetTextChange(Document document, int position) + { + var generator = SyntaxGenerator.GetGenerator(document); + var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty()); + + return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); + } + + protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) + { + GetIfStatementConditionAndCursorPosition(caretTarget, out _, out var cursorPosition); + + // Place at the end of the node specified for cursor position. + // Is the statement node in C# and the "Then" keyword + return cursorPosition; + } + + protected override async Task AnnotateNodesToReformatAsync(Document document, + SyntaxAnnotation findSnippetAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position, syntaxFacts); + if (snippetExpressionNode is null) + { + return root; + } + + var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(findSnippetAnnotation, cursorAnnotation, Simplifier.Annotation, Formatter.Annotation); + return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); + } + + protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + GetIfStatementConditionAndCursorPosition(node, out var condition, out var unusedVariable); + arrayBuilder.Add(new SnippetPlaceholder(identifier: condition.ToString(), placeholderPositions: ImmutableArray.Create(condition.SpanStart))); + + return arrayBuilder.ToImmutableArray(); + } + + protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts) + { + var closestNode = root.FindNode(TextSpan.FromBounds(position, position), getInnermostNodeForTie: true); + + var nearestStatement = closestNode.DescendantNodesAndSelf(syntaxFacts.IsIfStatement).FirstOrDefault(); + + if (nearestStatement is null) + { + return null; + } + + // Checking to see if that expression statement that we found is + // starting at the same position as the position we inserted + // the if statement. + if (nearestStatement.SpanStart != position) + { + return null; + } + + return nearestStatement; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs new file mode 100644 index 0000000000000..0a758e1a0fa58 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -0,0 +1,213 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.AddImport; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeCleanup; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ExtractMethod; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal abstract class AbstractSnippetProvider : ISnippetProvider + { + public abstract string SnippetIdentifier { get; } + public abstract string SnippetDisplayName { get; } + + protected readonly SyntaxAnnotation _cursorAnnotation = new(); + protected readonly SyntaxAnnotation _findSnippetAnnotation = new(); + + /// + /// Implemented by each SnippetProvider to determine if that particular position is a valid + /// location for the snippet to be inserted. + /// + protected abstract Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken); + + /// + /// Generates the new snippet's TextChanges that are being inserted into the document + /// + protected abstract Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken); + + /// + /// Method for each snippet to locate the inserted SyntaxNode to reformat + /// + protected abstract Task AnnotateNodesToReformatAsync(Document document, SyntaxAnnotation reformatAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken); + protected abstract int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget); + + /// + /// Every SnippetProvider will need a method to retrieve the "main" snippet syntax once it has been inserted as a TextChange. + /// + protected abstract SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts); + + /// + /// Method to find the locations that must be renamed and where tab stops must be inserted into the snippet. + /// + protected abstract ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken); + + /// + /// Determines if the location is valid for a snippet, + /// if so, then it creates a SnippetData. + /// + public async Task GetSnippetDataAsync(Document document, int position, CancellationToken cancellationToken) + { + var syntaxFacts = document.GetRequiredLanguageService(); + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (syntaxFacts.IsInNonUserCode(syntaxTree, position, cancellationToken)) + { + return null; + } + + if (!await IsValidSnippetLocationAsync(document, position, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + return new SnippetData(SnippetDisplayName, SnippetIdentifier); + } + + /// + /// Handles all the work to generate the Snippet. + /// Reformats the document with the snippet TextChange and annotates + /// appropriately for the cursor to get the target cursor position. + /// + public async Task GetSnippetAsync(Document document, int position, CancellationToken cancellationToken) + { + var syntaxFacts = document.GetRequiredLanguageService(); + + // Generates the snippet as a list of textchanges + var textChanges = await GenerateSnippetTextChangesAsync(document, position, cancellationToken).ConfigureAwait(false); + + // Applies the snippet textchanges to the document + var snippetDocument = await GetDocumentWithSnippetAsync(document, textChanges, cancellationToken).ConfigureAwait(false); + + // Finds the inserted snippet and replaces the node in the document with a node that has added trivia + // since all trivia is removed when converted to a TextChange. + var snippetWithTriviaDocument = await GetDocumentWithSnippetAndTriviaAsync(snippetDocument, position, syntaxFacts, cancellationToken).ConfigureAwait(false); + + // Adds annotations to inserted snippet to be formatted, simplified, add imports if needed, etc. + var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetWithTriviaDocument, position, cancellationToken).ConfigureAwait(false); + + // Goes through and calls upon the formatting engines that the previous step annotated. + var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); + + var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).FirstOrDefault(); + var mainChangeNode = reformattedRoot.GetAnnotatedNodes(_findSnippetAnnotation).FirstOrDefault(); + + // All the TextChanges from the original document. Will include any imports (if necessary) and all snippet associated + // changes after having been formatted. + var changes = await reformattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + + // Gets a listing of the identifiers that need to be found in the snippet TextChange + // and their associated TextSpan so they can later be converted into an LSP snippet format. + var placeholders = GetPlaceHolderLocationsList(mainChangeNode, syntaxFacts, cancellationToken); + + // All the changes from the original document to the most updated. Will later be + // collpased into one collapsed TextChange. + var changesArray = changes.ToImmutableArray(); + return new SnippetChange( + textChanges: changesArray, + cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget), + placeholders: placeholders); + } + + /// + /// Descends into the inserted snippet to add back trivia on every token. + /// + private static SyntaxNode? GenerateElasticTriviaForSyntax(ISyntaxFacts syntaxFacts, SyntaxNode? node) + { + if (node is null) + { + return null; + } + + var nodeWithTrivia = node.ReplaceTokens(node.DescendantTokens(descendIntoTrivia: true), + (oldtoken, _) => oldtoken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation) + .WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker) + .WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker)); + + return nodeWithTrivia; + } + + private async Task CleanupDocumentAsync( + Document document, CancellationToken cancellationToken) + { + if (document.SupportsSyntaxTree) + { + var addImportPlacementOptions = await document.GetAddImportPlacementOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); + var simplifierOptions = await document.GetSimplifierOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); + var syntaxFormattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); + + document = await ImportAdder.AddImportsFromSymbolAnnotationAsync( + document, _findSnippetAnnotation, addImportPlacementOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + + document = await Simplifier.ReduceAsync(document, _findSnippetAnnotation, simplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + + // format any node with explicit formatter annotation + document = await Formatter.FormatAsync(document, _findSnippetAnnotation, syntaxFormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + + // format any elastic whitespace + document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, syntaxFormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + return document; + } + + /// + /// Locates the snippet that was inserted. Generates trivia for every token in that syntaxnode. + /// Replaces the SyntaxNodes and gets back the new document. + /// + private async Task GetDocumentWithSnippetAndTriviaAsync(Document snippetDocument, int position, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + { + var root = await snippetDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var nearestStatement = FindAddedSnippetSyntaxNode(root, position, syntaxFacts); + + if (nearestStatement is null) + { + return snippetDocument; + } + + var nearestStatementWithTrivia = GenerateElasticTriviaForSyntax(syntaxFacts, nearestStatement); + + if (nearestStatementWithTrivia is null) + { + return snippetDocument; + } + + root = root.ReplaceNode(nearestStatement, nearestStatementWithTrivia); + return snippetDocument.WithSyntaxRoot(root); + } + + private static async Task GetDocumentWithSnippetAsync(Document document, ImmutableArray snippets, CancellationToken cancellationToken) + { + var originalText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + originalText = originalText.WithChanges(snippets); + var snippetDocument = document.WithText(originalText); + + return snippetDocument; + } + + private async Task AddFormatAnnotationAsync(Document document, int position, CancellationToken cancellationToken) + { + var annotatedSnippetRoot = await AnnotateNodesToReformatAsync(document, _findSnippetAnnotation, _cursorAnnotation, position, cancellationToken).ConfigureAwait(false); + document = document.WithSyntaxRoot(annotatedSnippetRoot); + return document; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.cs new file mode 100644 index 0000000000000..ec5dfa51c773c --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.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; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders +{ + internal interface ISnippetProvider + { + /// + /// What we use to identify each SnippetProvider for easier retrieval + /// + string SnippetIdentifier { get; } + + /// + /// What is being displayed for the Snippet on the Completion list + /// + string SnippetDisplayName { get; } + + /// + /// Determines if a snippet can exist at a particular location. + /// + Task GetSnippetDataAsync(Document document, int position, CancellationToken cancellationToken); + + /// + /// Gets the Snippet from the corresponding snippet provider. + /// + Task GetSnippetAsync(Document document, int position, CancellationToken cancellationToken); + } +} diff --git a/src/Features/Core/Portable/Wrapping/AbstractCodeActionComputer.cs b/src/Features/Core/Portable/Wrapping/AbstractCodeActionComputer.cs index 597bc7130a7a7..72620386d28ca 100644 --- a/src/Features/Core/Portable/Wrapping/AbstractCodeActionComputer.cs +++ b/src/Features/Core/Portable/Wrapping/AbstractCodeActionComputer.cs @@ -93,6 +93,7 @@ protected string GetIndentationAfter(SyntaxNodeOrToken nodeOrToken, FormattingOp var newSourceText = OriginalSourceText.WithChanges(new TextChange(new TextSpan(nodeOrToken.Span.End, 0), newLine)); newSourceText = newSourceText.WithChanges( new TextChange(TextSpan.FromBounds(nodeOrToken.Span.End + newLine.Length, newSourceText.Length), "")); + var newDocument = OriginalDocument.WithText(newSourceText); // The only auto-formatting option that's relevant is indent style. Others only control behavior on typing. @@ -100,8 +101,12 @@ protected string GetIndentationAfter(SyntaxNodeOrToken nodeOrToken, FormattingOp var indentationService = Wrapper.IndentationService; var originalLineNumber = newSourceText.Lines.GetLineFromPosition(nodeOrToken.Span.End).LineNumber; + + // TODO: should be async https://github.com/dotnet/roslyn/issues/61998 + var newParsedDocument = ParsedDocument.CreateSynchronously(newDocument, CancellationToken); + var desiredIndentation = indentationService.GetIndentation( - newDocument, originalLineNumber + 1, + newParsedDocument, originalLineNumber + 1, indentationOptions, CancellationToken); diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 51cab253a1975..98ca114537c0f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -960,6 +960,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Inline refaktoring metody {0} se zachováním deklarace + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Nedostatek šestnáctkových číslic @@ -1315,6 +1320,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Povýšit členy na základní typ... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Před kvantifikátorem {x,y} není nic uvedeno. @@ -2525,6 +2540,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Nahradit podmíněný výraz pomocí příkazů + + required + required + Used in the object initializer completion. + Resolve conflict markers Vyřešit značky konfliktů @@ -2970,6 +2990,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Zalamování + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Pro přepínání kontextů můžete použít navigační panel. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 37b2c2db18071..1a18d48f7fd6f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -960,6 +960,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d "{0}" inline einbinden und beibehalten + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Nicht genügend Hexadezimalziffern. @@ -1315,6 +1320,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Member zum Basistyp ziehen... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Quantifizierer {x,y} nach nichts. @@ -2525,6 +2540,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Bedingter Ausdruck durch Anweisungen ersetzen + + required + required + Used in the object initializer completion. + Resolve conflict markers Konfliktmarkierungen auflösen @@ -2970,6 +2990,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Umbruch + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Sie können die Navigationsleiste verwenden, um den Kontext zu wechseln. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 2016a98cc9df5..fd5b8253b37e0 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -960,6 +960,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Alinear y mantener "{0}" + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Insuficientes dígitos hexadecimales @@ -1315,6 +1320,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Extraer miembros hasta el tipo de base... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Cuantificador {x, y} después de nada @@ -2525,6 +2540,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Reemplazar expresión condicional con instrucciones + + required + required + Used in the object initializer completion. + Resolve conflict markers Resolver los marcadores de conflicto @@ -2970,6 +2990,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Ajuste + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Puede usar la barra de navegación para cambiar contextos. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index fa87ea5c5cf4a..1060ca2e56faf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -960,6 +960,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Inline et conserver '{0}' + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Chiffres hexadécimaux insuffisants @@ -1315,6 +1320,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Tirer les membres jusqu'au type de base... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Le quantificateur {x,y} ne suit rien @@ -2525,6 +2540,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Remplacer l’expression conditionnelle par des instructions + + required + required + Used in the object initializer completion. + Resolve conflict markers Résoudre les marqueurs de conflit @@ -2970,6 +2990,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Enveloppement + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Vous pouvez utiliser la barre de navigation pour changer de contexte. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index c5766946d44c1..1e28eaffcbadd 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -960,6 +960,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Imposta come inline e mantieni '{0}' + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Cifre esadecimali insufficienti @@ -1315,6 +1320,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Esegui pull dei membri fino al tipo di base... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Il quantificatore {x,y} non segue alcun elemento @@ -2525,6 +2540,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Sostituisci espressione condizionale con istruzioni + + required + required + Used in the object initializer completion. + Resolve conflict markers Risolvi gli indicatori di conflitto @@ -2970,6 +2990,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Ritorno a capo + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Per cambiare contesto, si può usare la barra di spostamento. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index ac4d97fb13502..219fe86dedb1c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma インラインおよび '{0}' の保持 + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits 16 進数の数字が正しくありません @@ -1315,6 +1320,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 基本型のメンバーをプル... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing 量指定子 {x,y} の前に何もありません @@ -2525,6 +2540,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 条件式をステートメントに置き換える + + required + required + Used in the object initializer completion. + Resolve conflict markers 競合マーカーの解決 @@ -2970,6 +2990,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 折り返し + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. ナビゲーション バーを使用してコンテキストを切り替えることができます。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index cbc5b3f092112..2c1d9e3bae111 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) 인라인으로 지정 및 유지 + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits 16진수가 부족합니다. @@ -1315,6 +1320,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 기본 형식까지 멤버를 풀... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing 수량자 {x,y} 앞에 아무 것도 없습니다. @@ -2525,6 +2540,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 조건식을 명령문으로 바꾸기 + + required + required + Used in the object initializer completion. + Resolve conflict markers 충돌 표식 확인 @@ -2970,6 +2990,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 래핑 + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. 탐색 모음을 사용하여 컨텍스트를 전환할 수 있습니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 479810bc0fa32..19d1da19dc7c6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -960,6 +960,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Wstaw środwierszowo i zachowaj metodę „{0}” + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Zbyt mało cyfr szesnastkowych @@ -1315,6 +1320,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Ściągnij składowe aż do typu bazowego... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Nic nie występuje przed kwantyfikatorem {x,y} @@ -2525,6 +2540,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Zamień wyrażenie warunkowe na instrukcje + + required + required + Used in the object initializer completion. + Resolve conflict markers Rozwiąż znaczniki konfliktów @@ -2970,6 +2990,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Zawijanie + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Za pomocą paska nawigacyjnego można przełączać konteksty. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index fc927ddc87792..f144264920ed9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -960,6 +960,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Embutir e manter '{0}' + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Dígitos hexadecimais insuficientes @@ -1315,6 +1320,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Efetuar pull de membros até o tipo base... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Nada precede o quantificador {x,y} @@ -2525,6 +2540,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Substituir expressão condicional por instruções + + required + required + Used in the object initializer completion. + Resolve conflict markers Resolver marcadores de conflitos @@ -2970,6 +2990,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Quebra de linha + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Você pode usar a barra de navegação para mudar de contexto. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index cfef8fdd0d25a..b8ca9cd2e41ad 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Сделать "{0}" встроенным и сохранить его + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Недостаточно шестнадцатеричных цифр @@ -1315,6 +1320,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Извлечь элементы до базового типа... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Отсутствуют элементы перед квантификатором {x,y} @@ -2525,6 +2540,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Заменить условное выражение операторами + + required + required + Used in the object initializer completion. + Resolve conflict markers Разрешение меток конфликтов @@ -2970,6 +2990,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Перенос по словам + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Для переключения контекстов можно использовать панель навигации. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 9985b11538e46..d4ff250b089bf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -960,6 +960,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' öğesini satır içine al ve koru + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits Yetersiz onaltılık basamak @@ -1315,6 +1320,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Üyeleri temel türe çek... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing Niceleyici {x, y} hiçbir şeyi takip etmiyor @@ -2525,6 +2540,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri Koşullu ifadeyi deyimlerle değiştirin + + required + required + Used in the object initializer completion. + Resolve conflict markers Çakışma işaretçilerini çözümle @@ -2970,6 +2990,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri Kaydırma + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. Bağlamlarda geçiş yapmak için gezinti çubuğunu kullanabilirsiniz. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 16f5d7b9b11b2..56fbae53b4f62 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 内联并保留“{0}” + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits 无效的十六进制数字 @@ -1315,6 +1320,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 将成员拉到基类... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing 限定符 {x,y} 前没有任何内容 @@ -2525,6 +2540,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 将条件表达式替换为语句 + + required + required + Used in the object initializer completion. + Resolve conflict markers 解决冲突标记 @@ -2970,6 +2990,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 换行 + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. 可使用导航栏切换上下文。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 9af0005c475ba..a001043cf4802 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 內嵌並保留 '{0}' + + Insert an 'if' statement + Insert an 'if' statement + + Insufficient hexadecimal digits 十六進位數位不足 @@ -1315,6 +1320,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 將成員提取直到基底類型... + + Pull selected members up + Pull selected members up + + + + Pull selected members up to {0} + Pull selected members up to {0} + + Quantifier {x,y} following nothing 數量詞 {x,y} 前面沒有任何項目 @@ -2525,6 +2540,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 以陳述式取代條件運算式 + + required + required + Used in the object initializer completion. + Resolve conflict markers 解決衝突標記 @@ -2970,6 +2990,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 換行 + + Write to the console + Write to the console + + You can use the navigation bar to switch contexts. 您可以使用導覽列切換內容。 diff --git a/src/Features/LanguageServer/Protocol/Features/Options/CompletionOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/CompletionOptionsStorage.cs index 86fe016f831e9..8f88c40e10cba 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/CompletionOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/CompletionOptionsStorage.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Completion.Providers.Snippets; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Completion; @@ -27,7 +28,9 @@ public static CompletionOptions GetCompletionOptions(this IGlobalOptionService o ProvideRegexCompletions = options.GetOption(ProvideRegexCompletions, language), ForceExpandedCompletionIndexCreation = options.GetOption(ForceExpandedCompletionIndexCreation), UpdateImportCompletionCacheInBackground = options.GetOption(UpdateImportCompletionCacheInBackground), - NamingStyleFallbackOptions = options.GetNamingStylePreferences(language) + NamingStyleFallbackOptions = options.GetNamingStylePreferences(language), + ShowNewSnippetExperience = options.GetOption(ShowNewSnippetExperience, language), + SnippetCompletion = options.GetOption(ShowNewSnippetExperienceFeatureFlag) }; // feature flags @@ -40,6 +43,10 @@ public static CompletionOptions GetCompletionOptions(this IGlobalOptionService o CompletionOptions.Default.UnnamedSymbolCompletionDisabled, new FeatureFlagStorageLocation("Roslyn.UnnamedSymbolCompletionDisabled")); + public static readonly Option2 ShowNewSnippetExperienceFeatureFlag = new(nameof(CompletionOptions), nameof(ShowNewSnippetExperienceFeatureFlag), + CompletionOptions.Default.SnippetCompletion, + new FeatureFlagStorageLocation("Roslyn.SnippetCompletion")); + // This is serialized by the Visual Studio-specific LanguageSettingsPersister public static readonly PerLanguageOption2 HideAdvancedMembers = new(nameof(CompletionOptions), nameof(HideAdvancedMembers), CompletionOptions.Default.HideAdvancedMembers); @@ -99,4 +106,8 @@ public static readonly Option2 UpdateImportCompletionCacheInBackground nameof(ProvideDateAndTimeCompletions), CompletionOptions.Default.ProvideDateAndTimeCompletions, storageLocation: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.ProvideDateAndTimeCompletions")); + + public static readonly PerLanguageOption2 ShowNewSnippetExperience + = new(nameof(CompletionOptions), nameof(ShowNewSnippetExperience), CompletionOptions.Default.ShowNewSnippetExperience, + storageLocation: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.ShowNewSnippetExperience")); } diff --git a/src/Features/LanguageServer/Protocol/Features/Options/DocumentationCommentOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/DocumentationCommentOptionsStorage.cs index 0ab002c56b994..b8596c9b3ebeb 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/DocumentationCommentOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/DocumentationCommentOptionsStorage.cs @@ -11,15 +11,6 @@ namespace Microsoft.CodeAnalysis.DocumentationComments; internal static class DocumentationCommentOptionsStorage { - public static async ValueTask GetDocumentationCommentOptionsAsync(this Document document, IGlobalOptionService globalOptions, CancellationToken cancellationToken) - { - var lineFormattingOptions = await document.GetLineFormattingOptionsAsync(globalOptions, cancellationToken).ConfigureAwait(false); - return new() - { - LineFormatting = lineFormattingOptions, - AutoXmlDocCommentGeneration = globalOptions.GetOption(AutoXmlDocCommentGeneration, document.Project.Language), - }; - } public static DocumentationCommentOptions GetDocumentationCommentOptions(this IGlobalOptionService globalOptions, LineFormattingOptions lineFormatting, string language) => new() diff --git a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs index a3d39b50891f7..9a00da65a639f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -146,7 +146,7 @@ public OnAutoInsertHandler( } var (service, context) = serviceAndContext.Value; - var postReturnEdit = await service.GetTextChangeAfterReturnAsync(context, options, cancellationToken).ConfigureAwait(false); + var postReturnEdit = service.GetTextChangeAfterReturn(context, options, cancellationToken); if (postReturnEdit == null) { return null; @@ -235,9 +235,11 @@ static string GetTextChangeTextWithCaretAtLocation(SourceText sourceText, TextCh _ => throw new ArgumentException($"Language {document.Project.Language} is not recognized for OnAutoInsert") }; + var parsedDocument = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + foreach (var service in servicesForDocument) { - var context = await service.GetCompletedBraceContextAsync(document, caretLocation, cancellationToken).ConfigureAwait(false); + var context = service.GetCompletedBraceContext(parsedDocument, caretLocation); if (context != null) { return (service, context.Value); diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/BracketBraceCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/BracketBraceCompletionService.vb index 768ccbd1cca6e..1f8d74b8e4398 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/BracketBraceCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/BracketBraceCompletionService.vb @@ -27,8 +27,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Protected Overrides ReadOnly Property OpeningBrace As Char = Bracket.OpenCharacter Protected Overrides ReadOnly Property ClosingBrace As Char = Bracket.CloseCharacter - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken) End Function Protected Overrides Function IsValidOpeningBraceToken(token As SyntaxToken) As Boolean diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/CurlyBraceCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/CurlyBraceCompletionService.vb index f70a15678af9f..c650e77006045 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/CurlyBraceCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/CurlyBraceCompletionService.vb @@ -23,16 +23,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Protected Overrides ReadOnly Property OpeningBrace As Char = CurlyBrace.OpenCharacter Protected Overrides ReadOnly Property ClosingBrace As Char = CurlyBrace.CloseCharacter - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken) End Function - Public Overrides Async Function CanProvideBraceCompletionAsync(brace As Char, openingPosition As Integer, document As Document, cancellationToken As CancellationToken) As Task(Of Boolean) - If OpeningBrace = brace And Await InterpolationBraceCompletionService.IsPositionInInterpolationContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(False) Then + Public Overrides Function CanProvideBraceCompletion(brace As Char, openingPosition As Integer, document As ParsedDocument, cancellationToken As CancellationToken) As Boolean + If OpeningBrace = brace And InterpolationBraceCompletionService.IsPositionInInterpolationContext(document, openingPosition) Then Return False End If - Return Await MyBase.CanProvideBraceCompletionAsync(brace, openingPosition, document, cancellationToken).ConfigureAwait(False) + Return MyBase.CanProvideBraceCompletion(brace, openingPosition, document, cancellationToken) End Function Protected Overrides Function IsValidOpeningBraceToken(token As SyntaxToken) As Boolean diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.vb index f481e109ae032..74272722ad892 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/InterpolatedStringBraceCompletionService.vb @@ -29,12 +29,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Return IsValidOpeningBraceToken(token) AndAlso token.Span.End - 1 = position End Function - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeWithValidClosingToken(context) End Function - Public Overrides Async Function CanProvideBraceCompletionAsync(brace As Char, openingPosition As Integer, document As Document, cancellationToken As CancellationToken) As Task(Of Boolean) - Return OpeningBrace = brace And Await IsPositionInInterpolatedStringContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(False) + Public Overrides Function CanProvideBraceCompletion(brace As Char, openingPosition As Integer, document As ParsedDocument, cancellationToken As CancellationToken) As Boolean + Return OpeningBrace = brace And IsPositionInInterpolatedStringContext(document, openingPosition) End Function Protected Overrides Function IsValidOpeningBraceToken(token As SyntaxToken) As Boolean @@ -45,15 +45,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Return token.IsKind(SyntaxKind.DoubleQuoteToken) End Function - Public Shared Async Function IsPositionInInterpolatedStringContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) + Public Shared Function IsPositionInInterpolatedStringContext(document As ParsedDocument, position As Integer) As Boolean If position = 0 Then Return False End If - Dim text = Await document.GetTextAsync(cancellationToken).ConfigureAwait(False) - ' Position can be in an interpolated string if the preceding character is a $ - Return text(position - 1) = "$"c + Return document.Text(position - 1) = "$"c End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/InterpolationBraceCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/InterpolationBraceCompletionService.vb index fcf0b759b0e36..8b32312ce4f55 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/InterpolationBraceCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/InterpolationBraceCompletionService.vb @@ -28,12 +28,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Return IsValidOpeningBraceToken(token) End Function - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeWithValidClosingToken(context) End Function - Public Overrides Async Function CanProvideBraceCompletionAsync(brace As Char, openingPosition As Integer, document As Document, cancellationToken As CancellationToken) As Task(Of Boolean) - Return OpeningBrace = brace And Await IsPositionInInterpolationContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(False) + Public Overrides Function CanProvideBraceCompletion(brace As Char, openingPosition As Integer, document As ParsedDocument, cancellationToken As CancellationToken) As Boolean + Return OpeningBrace = brace And IsPositionInInterpolationContext(document, openingPosition) End Function Protected Overrides Function IsValidOpeningBraceToken(token As SyntaxToken) As Boolean @@ -45,7 +45,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Return token.IsKind(SyntaxKind.CloseBraceToken) End Function - Public Shared Async Function IsPositionInInterpolationContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) + Public Shared Function IsPositionInInterpolationContext(document As ParsedDocument, position As Integer) As Boolean If position = 0 Then Return False End If @@ -53,14 +53,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion ' First, check to see if the character to the left of the position is an open curly. ' If it is, we shouldn't complete because the user may be trying to escape a curly. ' E.g. they are trying to type $"{{" - If (Await CouldEscapePreviousOpenBraceAsync("{"c, position, document, cancellationToken).ConfigureAwait(False)) Then + If CouldEscapePreviousOpenBrace("{"c, position, document.Text) Then Return False End If ' Next, check to see if the token we're typing is part of an existing interpolated string. ' - Dim root = Await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(False) - Dim token = root.FindTokenOnRightOfPosition(position) + Dim token = document.Root.FindTokenOnRightOfPosition(position) If Not token.Span.IntersectsWith(position) Then Return False diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/LessAndGreaterThanCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/LessAndGreaterThanCompletionService.vb index 0ccf0ff748621..e39e024003fd1 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/LessAndGreaterThanCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/LessAndGreaterThanCompletionService.vb @@ -43,8 +43,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Return True End Function - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken) End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/ParenthesisBraceCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/ParenthesisBraceCompletionService.vb index 52acc51bc5c31..639f282a35fa1 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/ParenthesisBraceCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/ParenthesisBraceCompletionService.vb @@ -51,8 +51,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Return True End Function - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeInUserCodeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeInUserCodeWithValidClosingToken(context, cancellationToken) End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/BraceCompletion/StringLiteralBraceCompletionService.vb b/src/Features/VisualBasic/Portable/BraceCompletion/StringLiteralBraceCompletionService.vb index 4828a56dec3a3..6b3aa54ba318e 100644 --- a/src/Features/VisualBasic/Portable/BraceCompletion/StringLiteralBraceCompletionService.vb +++ b/src/Features/VisualBasic/Portable/BraceCompletion/StringLiteralBraceCompletionService.vb @@ -23,16 +23,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.BraceCompletion Protected Overrides ReadOnly Property OpeningBrace As Char = DoubleQuote.OpenCharacter Protected Overrides ReadOnly Property ClosingBrace As Char = DoubleQuote.CloseCharacter - Public Overrides Function AllowOverTypeAsync(context As BraceCompletionContext, cancellationToken As CancellationToken) As Task(Of Boolean) - Return AllowOverTypeWithValidClosingTokenAsync(context, cancellationToken) + Public Overrides Function AllowOverType(context As BraceCompletionContext, cancellationToken As CancellationToken) As Boolean + Return AllowOverTypeWithValidClosingToken(context) End Function - Public Overrides Async Function CanProvideBraceCompletionAsync(brace As Char, openingPosition As Integer, document As Document, cancellationToken As CancellationToken) As Task(Of Boolean) - If (OpeningBrace = brace And Await InterpolatedStringBraceCompletionService.IsPositionInInterpolatedStringContextAsync(document, openingPosition, cancellationToken).ConfigureAwait(False)) Then + Public Overrides Function CanProvideBraceCompletion(brace As Char, openingPosition As Integer, document As ParsedDocument, cancellationToken As CancellationToken) As Boolean + If OpeningBrace = brace And InterpolatedStringBraceCompletionService.IsPositionInInterpolatedStringContext(document, openingPosition) Then Return False End If - Return Await MyBase.CanProvideBraceCompletionAsync(brace, openingPosition, document, cancellationToken).ConfigureAwait(False) + Return MyBase.CanProvideBraceCompletion(brace, openingPosition, document, cancellationToken) End Function Protected Overrides Function IsValidOpeningBraceToken(token As SyntaxToken) As Boolean diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/MoveStaticMembers/VisualBasicMoveStaticMembersRefactoringProvider.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/MoveStaticMembers/VisualBasicMoveStaticMembersRefactoringProvider.vb index 042ddb09fc9cc..59231341cad84 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/MoveStaticMembers/VisualBasicMoveStaticMembersRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/MoveStaticMembers/VisualBasicMoveStaticMembersRefactoringProvider.vb @@ -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. +Imports System.Collections.Immutable Imports System.Composition Imports Microsoft.CodeAnalysis.CodeRefactorings Imports Microsoft.CodeAnalysis.Host.Mef @@ -18,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.MoveStaticMembers MyBase.New() End Sub - Protected Overrides Async Function GetSelectedNodeAsync(context As CodeRefactoringContext) As Task(Of SyntaxNode) + Protected Overrides Async Function GetSelectedNodesAsync(context As CodeRefactoringContext) As Task(Of ImmutableArray(Of SyntaxNode)) Return Await GetSelectedMemberDeclarationAsync(context).ConfigureAwait(False) End Function End Class diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/NodeSelectionHelpers.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/NodeSelectionHelpers.vb index e82b062351c20..ce8cb66bd2919 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/NodeSelectionHelpers.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/NodeSelectionHelpers.vb @@ -2,24 +2,48 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable +Imports System.Threading Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.LanguageServices Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings Friend Module NodeSelectionHelpers - Friend Async Function GetSelectedMemberDeclarationAsync(context As CodeRefactoringContext) As Task(Of SyntaxNode) - Dim methodMember = Await context.TryGetRelevantNodeAsync(Of MethodBaseSyntax)().ConfigureAwait(False) - If methodMember IsNot Nothing Then - Return methodMember + Friend Async Function GetSelectedMemberDeclarationAsync(context As CodeRefactoringContext) As Task(Of ImmutableArray(Of SyntaxNode)) + Dim document As Document = context.Document + Dim span As TextSpan = context.Span + Dim cancellationToken As CancellationToken = context.CancellationToken + If span.IsEmpty Then + ' MethodBaseSyntax also includes properties + Dim methodMember = Await context.TryGetRelevantNodeAsync(Of MethodBaseSyntax)().ConfigureAwait(False) + If methodMember IsNot Nothing Then + Return ImmutableArray.Create(Of SyntaxNode)(methodMember) + End If + ' Gets field variable declarations (not including the keywords like Public/Shared, etc), which are not methods + Dim fieldDeclaration = Await context.TryGetRelevantNodeAsync(Of FieldDeclarationSyntax).ConfigureAwait(False) + If fieldDeclaration Is Nothing Then + ' Gets the identifier + type of the field itself (ex. TestField As Integer), since it is nested in the variable declaration + ' And so the token's parent is not a variable declaration + Dim modifiedIdentifier = Await context.TryGetRelevantNodeAsync(Of ModifiedIdentifierSyntax).ConfigureAwait(False) + If modifiedIdentifier Is Nothing Then + Return ImmutableArray(Of SyntaxNode).Empty + Else + Return ImmutableArray.Create(Of SyntaxNode)(modifiedIdentifier) + End If + Else + ' Field declarations can contain multiple variables (each of which are a "member") + Return fieldDeclaration.Declarators.SelectMany(Function(vds) vds.Names).Cast(Of SyntaxNode).AsImmutable() + End If + Else + ' if the span is non-empty, then we get potentially multiple members + ' Note: even though this method handles the empty span case, we don't use it because it doesn't correctly + ' pick up on keywords before the declaration, such as "public static int". + ' We could potentially use it for every case if that behavior changes + Dim tree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False) + Return Await VisualBasicSelectedMembers.Instance.GetSelectedMembersAsync(tree, span, allowPartialSelection:=True, cancellationToken).ConfigureAwait(False) End If - ' Gets field variable declarations (not including the keywords like Public/Shared, etc), which are not methods - Dim fieldDeclaration = Await context.TryGetRelevantNodeAsync(Of FieldDeclarationSyntax).ConfigureAwait(False) - If fieldDeclaration IsNot Nothing Then - Return fieldDeclaration - End If - ' Gets the identifier + type of the field itself (ex. TestField As Integer), since it is nested in the variable declaration - ' And so the token's parent is not a variable declaration - Return Await context.TryGetRelevantNodeAsync(Of ModifiedIdentifierSyntax).ConfigureAwait(False) End Function End Module End Namespace diff --git a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb index 0c7f03ca1fa12..b8ffe9115df9a 100644 --- a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb @@ -5,6 +5,7 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.IntroduceVariable Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -15,7 +16,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable Inherits AbstractIntroduceParameterService(Of ExpressionSyntax, InvocationExpressionSyntax, ObjectCreationExpressionSyntax, IdentifierNameSyntax) - + Public Sub New() End Sub diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs index c89f4acbc8e50..057de7bb967f2 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs @@ -337,10 +337,7 @@ public async Task ResetAsync(InteractiveHostOptions optio var newService = CreateRemoteService(options, skipInitialization: false); var oldService = Interlocked.Exchange(ref _lazyRemoteService, newService); - if (oldService != null) - { - oldService.Dispose(); - } + oldService?.Dispose(); var initializedService = await TryGetOrCreateRemoteServiceAsync().ConfigureAwait(false); if (initializedService.Service == null) diff --git a/src/Scripting/Core/Hosting/AssemblyLoader/MetadataShadowCopyProvider.cs b/src/Scripting/Core/Hosting/AssemblyLoader/MetadataShadowCopyProvider.cs index 0a1fd9c67f6a1..613ea5944040c 100644 --- a/src/Scripting/Core/Hosting/AssemblyLoader/MetadataShadowCopyProvider.cs +++ b/src/Scripting/Core/Hosting/AssemblyLoader/MetadataShadowCopyProvider.cs @@ -366,10 +366,7 @@ public void SuppressShadowCopy(string originalPath) lock (Guard) { - if (_lazySuppressedFiles == null) - { - _lazySuppressedFiles = new HashSet(StringComparer.OrdinalIgnoreCase); - } + _lazySuppressedFiles ??= new HashSet(StringComparer.OrdinalIgnoreCase); _lazySuppressedFiles.Add(originalPath); } @@ -404,10 +401,7 @@ private CacheEntry CreateMetadataShadowCopy(string originalP { try { - if (ShadowCopyDirectory == null) - { - ShadowCopyDirectory = CreateUniqueDirectory(_baseDirectory); - } + ShadowCopyDirectory ??= CreateUniqueDirectory(_baseDirectory); // Create directory for the assembly. // If the assembly has any modules they have to be copied to the same directory @@ -490,10 +484,7 @@ private AssemblyMetadata CreateAssemblyMetadata(FileStream manifestModuleCopyStr { if (fault) { - if (manifestModule != null) - { - manifestModule.Dispose(); - } + manifestModule?.Dispose(); if (moduleBuilder != null) { diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs index 0e2d76d022210..9bd615a8d2ac6 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs @@ -36,10 +36,7 @@ private HashSet VisitedObjects { get { - if (_lazyVisitedObjects == null) - { - _lazyVisitedObjects = new HashSet(ReferenceEqualityComparer.Instance); - } + _lazyVisitedObjects ??= new HashSet(ReferenceEqualityComparer.Instance); return _lazyVisitedObjects; } @@ -604,10 +601,7 @@ private void FormatDictionaryMembers(Builder result, IDictionary dict, bool inli } finally { - if (disposable != null) - { - disposable.Dispose(); - } + disposable?.Dispose(); } } catch (Exception e) diff --git a/src/Scripting/Core/ScriptBuilder.cs b/src/Scripting/Core/ScriptBuilder.cs index 908836a5f2ba3..ee1d4ebe04a2b 100644 --- a/src/Scripting/Core/ScriptBuilder.cs +++ b/src/Scripting/Core/ScriptBuilder.cs @@ -90,10 +90,7 @@ internal Func> CreateExecutor(ScriptCompiler compiler, Comp // emit can fail due to compilation errors or because there is nothing to emit: ThrowIfAnyCompilationErrors(diagnostics, compiler.DiagnosticFormatter); - if (executor == null) - { - executor = (s) => Task.FromResult(default(T)); - } + executor ??= (s) => Task.FromResult(default(T)); return executor; } diff --git a/src/Test/Perf/Utilities/TestUtilities.cs b/src/Test/Perf/Utilities/TestUtilities.cs index 57b9ab96d5527..b94140906c468 100644 --- a/src/Test/Perf/Utilities/TestUtilities.cs +++ b/src/Test/Perf/Utilities/TestUtilities.cs @@ -121,10 +121,7 @@ public static ProcessResult ShellOut( string workingDirectory = null, CancellationToken cancellationToken = default(CancellationToken)) { - if (workingDirectory == null) - { - workingDirectory = AppDomain.CurrentDomain.BaseDirectory; - } + workingDirectory ??= AppDomain.CurrentDomain.BaseDirectory; var tcs = new TaskCompletionSource(); var startInfo = new ProcessStartInfo(file, args); diff --git a/src/Tools/BuildValidator/LocalReferenceResolver.cs b/src/Tools/BuildValidator/LocalReferenceResolver.cs index 3c93b7cb02796..4958110830330 100644 --- a/src/Tools/BuildValidator/LocalReferenceResolver.cs +++ b/src/Tools/BuildValidator/LocalReferenceResolver.cs @@ -61,10 +61,7 @@ public LocalReferenceResolver(Options options, ILoggerFactory loggerFactory) public static DirectoryInfo GetNugetCacheDirectory() { var nugetPackageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); - if (nugetPackageDirectory is null) - { - nugetPackageDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget"); - } + nugetPackageDirectory ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget"); return new DirectoryInfo(nugetPackageDirectory); } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/CommentSelection/FSharpCommentSelectionService.cs b/src/Tools/ExternalAccess/FSharp/Internal/CommentSelection/FSharpCommentSelectionService.cs index 41ef91a2f850f..a22ab48dce0d8 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/CommentSelection/FSharpCommentSelectionService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/CommentSelection/FSharpCommentSelectionService.cs @@ -26,19 +26,12 @@ public FSharpCommentSelectionService() { } - public Task FormatAsync(Document document, ImmutableArray changes, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) - { - return Task.FromResult(document); - } - - public Task GetInfoAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) - { - return Task.FromResult(new CommentSelectionInfo( + public CommentSelectionInfo GetInfo() + => new( supportsSingleLineComment: true, supportsBlockComment: true, singleLineCommentString: "//", blockCommentStartString: "(*", - blockCommentEndString: "*)")); - } + blockCommentEndString: "*)"); } } diff --git a/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs b/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs index 54faf1aea3097..f4ea93f41ec35 100644 --- a/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractClass { internal interface IOmniSharpExtractClassOptionsService { - Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ISymbol? selectedMember); + Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ImmutableArray selectedMembers); } internal sealed class OmniSharpExtractClassOptions diff --git a/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs b/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs index 6d4abad23938a..75d800865f797 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractClass; @@ -25,9 +27,9 @@ public OmniSharpExtractClassOptionsService(IOmniSharpExtractClassOptionsService _omniSharpExtractClassOptionsService = omniSharpExtractClassOptionsService; } - public async Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ISymbol? selectedMember, CancellationToken cancellationToken) + public async Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ImmutableArray selectedMembers, CancellationToken cancellationToken) { - var result = await _omniSharpExtractClassOptionsService.GetExtractClassOptionsAsync(document, originalType, selectedMember).ConfigureAwait(false); + var result = await _omniSharpExtractClassOptionsService.GetExtractClassOptionsAsync(document, originalType, selectedMembers).ConfigureAwait(false); return result == null ? null : new ExtractClassOptions( diff --git a/src/Tools/ExternalAccess/OmniSharp/Options/IOmniSharpLineFormattingOptionsProvider.cs b/src/Tools/ExternalAccess/OmniSharp/Options/IOmniSharpLineFormattingOptionsProvider.cs new file mode 100644 index 0000000000000..8b92e27116e95 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Options/IOmniSharpLineFormattingOptionsProvider.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. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Options +{ + internal interface IOmniSharpLineFormattingOptionsProvider + { + OmniSharpLineFormattingOptions GetLineFormattingOptions(); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Options/OmniSharpLineFormattingOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Options/OmniSharpLineFormattingOptions.cs new file mode 100644 index 0000000000000..659cf731b54ef --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Options/OmniSharpLineFormattingOptions.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; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Options +{ + internal sealed record class OmniSharpLineFormattingOptions + { + public bool UseTabs { get; init; } = false; + public int TabSize { get; init; } = 4; + public int IndentationSize { get; init; } = 4; + public string NewLine { get; init; } = Environment.NewLine; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Options/OmnisharpLegacyGlobalOptionsWorkspaceService.cs b/src/Tools/ExternalAccess/OmniSharp/Options/OmnisharpLegacyGlobalOptionsWorkspaceService.cs index a61a523455495..de690974d75e3 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Options/OmnisharpLegacyGlobalOptionsWorkspaceService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Options/OmnisharpLegacyGlobalOptionsWorkspaceService.cs @@ -2,17 +2,16 @@ // 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.Formatting; -using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.InlineHints; -using Microsoft.CodeAnalysis.Options; using System.Threading.Tasks; using System.Threading; +using System; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.CodeCleanup; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Options { @@ -26,9 +25,9 @@ internal sealed class OmnisharpLegacyGlobalOptionsWorkspaceService : ILegacyGlob [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public OmnisharpLegacyGlobalOptionsWorkspaceService() + public OmnisharpLegacyGlobalOptionsWorkspaceService(IOmniSharpLineFormattingOptionsProvider lineFormattingOptionsProvider) { - _provider = new OmniSharpCleanCodeGenerationOptionsProvider(); + _provider = new OmniSharpCleanCodeGenerationOptionsProvider(lineFormattingOptionsProvider); } public bool RazorUseTabs @@ -76,9 +75,30 @@ public void SetGenerateConstructorFromMembersOptionsAddNullChecks(string languag internal sealed class OmniSharpCleanCodeGenerationOptionsProvider : AbstractCleanCodeGenerationOptionsProvider { + private readonly IOmniSharpLineFormattingOptionsProvider _lineFormattingOptionsProvider; + + public OmniSharpCleanCodeGenerationOptionsProvider(IOmniSharpLineFormattingOptionsProvider lineFormattingOptionsProvider) + { + _lineFormattingOptionsProvider = lineFormattingOptionsProvider; + } + public override ValueTask GetCleanCodeGenerationOptionsAsync(HostLanguageServices languageServices, CancellationToken cancellationToken) { - return new ValueTask(CleanCodeGenerationOptions.GetDefault(languageServices)); + var lineFormattingOptions = _lineFormattingOptionsProvider.GetLineFormattingOptions(); + var codeGenerationOptions = CleanCodeGenerationOptions.GetDefault(languageServices) with + { + CleanupOptions = CodeCleanupOptions.GetDefault(languageServices) with + { + FormattingOptions = SyntaxFormattingOptions.GetDefault(languageServices).With(new LineFormattingOptions + { + IndentationSize = lineFormattingOptions.IndentationSize, + TabSize = lineFormattingOptions.TabSize, + UseTabs = lineFormattingOptions.UseTabs, + NewLine = lineFormattingOptions.NewLine, + }) + } + }; + return new ValueTask(codeGenerationOptions); } } } diff --git a/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj b/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj index 28df8f7392000..62f6c171e1609 100644 --- a/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj +++ b/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj @@ -15,6 +15,7 @@ + diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index 7f49e6d5786ac..9191602c7b664 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -655,4 +655,7 @@ Collapse usings on file open + + Show new snippet experience (experimental) + \ 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 3d667d025dd2d..604362b31f96c 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs @@ -39,16 +39,14 @@ internal partial class CSharpCodeModelService : AbstractCodeModelService { internal CSharpCodeModelService( HostLanguageServices languageServiceProvider, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IEnumerable refactorNotifyServices, - IGlobalOptionService globalOptions, IThreadingContext threadingContext) : base(languageServiceProvider, - editorOptionsFactoryService, + editorOptionsService, refactorNotifyServices, BlankLineInGeneratedMethodFormattingRule.Instance, EndRegionFormattingRule.Instance, - globalOptions, threadingContext) { } @@ -3562,17 +3560,11 @@ private static bool IsAutoImplementedProperty(PropertyDeclarationSyntax property switch (accessor.Kind()) { case SyntaxKind.GetAccessorDeclaration: - if (getAccessor == null) - { - getAccessor = accessor; - } + getAccessor ??= accessor; break; case SyntaxKind.SetAccessorDeclaration: - if (setAccessor == null) - { - setAccessor = accessor; - } + setAccessor ??= accessor; break; } diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs index af3db5d41cfd8..18843e2c61ab8 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs @@ -19,26 +19,23 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel [ExportLanguageServiceFactory(typeof(ICodeModelService), LanguageNames.CSharp), Shared] internal partial class CSharpCodeModelServiceFactory : ILanguageServiceFactory { - private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; + private readonly EditorOptionsService _editorOptionsService; private readonly IEnumerable _refactorNotifyServices; - private readonly IGlobalOptionService _globalOptions; private readonly IThreadingContext _threadingContext; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpCodeModelServiceFactory( - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, [ImportMany] IEnumerable refactorNotifyServices, - IGlobalOptionService globalOptions, IThreadingContext threadingContext) { - _editorOptionsFactoryService = editorOptionsFactoryService; + _editorOptionsService = editorOptionsService; _refactorNotifyServices = refactorNotifyServices; - _globalOptions = globalOptions; _threadingContext = threadingContext; } public ILanguageService CreateLanguageService(HostLanguageServices provider) - => new CSharpCodeModelService(provider, _editorOptionsFactoryService, _refactorNotifyServices, _globalOptions, _threadingContext); + => new CSharpCodeModelService(provider, _editorOptionsService, _refactorNotifyServices, _threadingContext); } } diff --git a/src/VisualStudio/CSharp/Impl/Interactive/CSharpInteractiveCommandHandler.cs b/src/VisualStudio/CSharp/Impl/Interactive/CSharpInteractiveCommandHandler.cs index f635bc344e563..3d74204805446 100644 --- a/src/VisualStudio/CSharp/Impl/Interactive/CSharpInteractiveCommandHandler.cs +++ b/src/VisualStudio/CSharp/Impl/Interactive/CSharpInteractiveCommandHandler.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Interactive; +using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; @@ -30,9 +31,9 @@ public CSharpInteractiveCommandHandler( CSharpVsInteractiveWindowProvider interactiveWindowProvider, ISendToInteractiveSubmissionProvider sendToInteractiveSubmissionProvider, IContentTypeRegistryService contentTypeRegistryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IEditorOperationsFactoryService editorOperationsFactoryService) - : base(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService) + : base(contentTypeRegistryService, editorOptionsService, editorOperationsFactoryService) { _interactiveWindowProvider = interactiveWindowProvider; _sendToInteractiveSubmissionProvider = sendToInteractiveSubmissionProvider; diff --git a/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs b/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs index 7cf17e208452c..c520c29fd0bd4 100644 --- a/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs +++ b/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs @@ -18,6 +18,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using LanguageServiceGuids = Microsoft.VisualStudio.LanguageServices.Guids; @@ -28,8 +29,8 @@ internal sealed class CSharpVsInteractiveWindowProvider : VsInteractiveWindowPro { private readonly IThreadingContext _threadingContext; private readonly IAsynchronousOperationListener _listener; - private readonly IGlobalOptionService _globalOptions; private readonly ITextDocumentFactoryService _textDocumentFactoryService; + private readonly EditorOptionsService _editorOptionsService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -42,15 +43,15 @@ public CSharpVsInteractiveWindowProvider( IContentTypeRegistryService contentTypeRegistry, IInteractiveWindowCommandsFactory commandsFactory, [ImportMany] IInteractiveWindowCommand[] commands, - IGlobalOptionService globalOptions, ITextDocumentFactoryService textDocumentFactoryService, + EditorOptionsService editorOptionsService, VisualStudioWorkspace workspace) : base(serviceProvider, interactiveWindowFactory, classifierAggregator, contentTypeRegistry, commandsFactory, commands, workspace) { _threadingContext = threadingContext; _listener = listenerProvider.GetListener(FeatureAttribute.InteractiveEvaluator); - _globalOptions = globalOptions; _textDocumentFactoryService = textDocumentFactoryService; + _editorOptionsService = editorOptionsService; } protected override Guid LanguageServiceGuid => LanguageServiceGuids.CSharpLanguageServiceId; @@ -69,7 +70,6 @@ protected override CSharpInteractiveEvaluator CreateInteractiveEvaluator( VisualStudioWorkspace workspace) { return new CSharpInteractiveEvaluator( - _globalOptions, _threadingContext, _listener, contentTypeRegistry.GetContentType(ContentTypeNames.CSharpContentType), @@ -78,6 +78,7 @@ protected override CSharpInteractiveEvaluator CreateInteractiveEvaluator( CommandsFactory, Commands, _textDocumentFactoryService, + _editorOptionsService, CSharpInteractiveEvaluatorLanguageInfoProvider.Instance, Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); } diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index 3dde53e4cdb62..93be68796dd11 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -15,7 +15,7 @@ - diff --git a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml.cs index 3f668ff9d7f82..5193c137465eb 100644 --- a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml.cs @@ -26,6 +26,7 @@ public IntelliSenseOptionPageControl(OptionStore optionStore) : base(optionStore BindToOption(Show_completion_list_after_a_character_is_typed, CompletionOptionsStorage.TriggerOnTypingLetters, LanguageNames.CSharp); Show_completion_list_after_a_character_is_deleted.IsChecked = this.OptionStore.GetOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp) == true; Show_completion_list_after_a_character_is_deleted.IsEnabled = Show_completion_list_after_a_character_is_typed.IsChecked == true; + AddSearchHandler(Show_completion_list_after_a_character_is_deleted); BindToOption(Never_include_snippets, CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.NeverInclude, LanguageNames.CSharp); BindToOption(Always_include_snippets, CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude, LanguageNames.CSharp); @@ -39,8 +40,13 @@ public IntelliSenseOptionPageControl(OptionStore optionStore) : base(optionStore BindToOption(Automatically_show_completion_list_in_argument_lists, CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp); Show_items_from_unimported_namespaces.IsChecked = this.OptionStore.GetOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp); + AddSearchHandler(Show_items_from_unimported_namespaces); + Tab_twice_to_insert_arguments.IsChecked = this.OptionStore.GetOption(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.CSharp); + AddSearchHandler(Tab_twice_to_insert_arguments); + Show_new_snippet_experience.IsChecked = this.OptionStore.GetOption(CompletionOptionsStorage.ShowNewSnippetExperience, LanguageNames.CSharp); + AddSearchHandler(Show_new_snippet_experience); } private void Show_completion_list_after_a_character_is_typed_Checked(object sender, RoutedEventArgs e) @@ -70,5 +76,11 @@ private void Tab_twice_to_insert_arguments_CheckedChanged(object sender, RoutedE Tab_twice_to_insert_arguments.IsThreeState = false; this.OptionStore.SetOption(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.CSharp, value: Tab_twice_to_insert_arguments.IsChecked); } + + private void Show_new_snippet_experience_CheckedChanged(object sender, RoutedEventArgs e) + { + Show_new_snippet_experience.IsThreeState = false; + this.OptionStore.SetOption(CompletionOptionsStorage.ShowNewSnippetExperience, LanguageNames.CSharp, value: Show_new_snippet_experience.IsChecked); + } } } diff --git a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageStrings.cs index ce23a96563905..732d14be7798a 100644 --- a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageStrings.cs +++ b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageStrings.cs @@ -77,5 +77,8 @@ public static string Option_ShowSnippets public static string Automatically_show_completion_list_in_argument_lists => CSharpVSResources.Automatically_show_completion_list_in_argument_lists; + + public static string Option_Show_new_snippet_experience_experimental => + CSharpVSResources.Show_new_snippet_experience_experimental; } } diff --git a/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs b/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs index 769da32ea385b..fc398a028f31e 100644 --- a/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs +++ b/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs @@ -55,8 +55,8 @@ public SnippetCommandHandler( IVsEditorAdaptersFactoryService editorAdaptersFactoryService, SVsServiceProvider serviceProvider, [ImportMany] IEnumerable> argumentProviders, - IGlobalOptionService globalOptions) - : base(threadingContext, signatureHelpControllerProvider, editorCommandHandlerServiceFactory, editorAdaptersFactoryService, globalOptions, serviceProvider) + EditorOptionsService editorOptionsService) + : base(threadingContext, signatureHelpControllerProvider, editorCommandHandlerServiceFactory, editorAdaptersFactoryService, editorOptionsService, serviceProvider) { _argumentProviders = argumentProviders.ToImmutableArray(); } @@ -131,7 +131,7 @@ protected override AbstractSnippetExpansionClient GetSnippetExpansionClient(ITex EditorCommandHandlerServiceFactory, EditorAdaptersFactoryService, _argumentProviders, - GlobalOptions); + EditorOptionsService); textView.Properties.AddProperty(typeof(AbstractSnippetExpansionClient), expansionClient); } diff --git a/src/VisualStudio/CSharp/Impl/Snippets/SnippetExpansionClient.cs b/src/VisualStudio/CSharp/Impl/Snippets/SnippetExpansionClient.cs index 8146215f9b9b3..a2b2be8eddeed 100644 --- a/src/VisualStudio/CSharp/Impl/Snippets/SnippetExpansionClient.cs +++ b/src/VisualStudio/CSharp/Impl/Snippets/SnippetExpansionClient.cs @@ -45,7 +45,7 @@ public SnippetExpansionClient( IEditorCommandHandlerServiceFactory editorCommandHandlerServiceFactory, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, ImmutableArray> argumentProviders, - IGlobalOptionService globalOptions) + EditorOptionsService editorOptionsService) : base( threadingContext, languageServiceGuid, @@ -55,7 +55,7 @@ public SnippetExpansionClient( editorCommandHandlerServiceFactory, editorAdaptersFactoryService, argumentProviders, - globalOptions) + editorOptionsService) { } diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf index fe694a4b041ad..8564d5aaff1b2 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf @@ -182,6 +182,11 @@ Zobrazit položky z neimportovaných oborů názvů + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Zobrazit poznámky v Rychlých informacích diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf index a095750831d79..dc5bffdcb447d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf @@ -182,6 +182,11 @@ Elemente aus nicht importierten Namespaces anzeigen + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Hinweise in QuickInfo anzeigen diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf index aee978d8059a2..1935c60d77383 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf @@ -182,6 +182,11 @@ Mostrar elementos de espacios de nombres no importados + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Mostrar comentarios en Información rápida diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf index cea73095c5d60..adaeba798e6e0 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf @@ -182,6 +182,11 @@ Afficher les éléments des espaces de noms qui ne sont pas importés + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Afficher les notes dans Info express diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf index c802756fbff40..accb679fea18d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf @@ -182,6 +182,11 @@ Mostra elementi da spazi dei nomi non importati + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Mostra i commenti in Informazioni rapide diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf index 6b0f38b0a3794..a98692ec266c8 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf @@ -182,6 +182,11 @@ インポートされていない名前空間の項目を表示する + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info クイック ヒントに注釈を表示する diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf index 270f709dc6b60..583811fa05f21 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf @@ -182,6 +182,11 @@ 가져오지 않은 네임스페이스의 항목 표시 + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info 요약 정보에 설명 표시 diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf index 48e0d64ec7181..54d954de7e5bb 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf @@ -182,6 +182,11 @@ Pokaż elementy z nieimportowanych przestrzeni nazw + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Pokaż uwagi w szybkich podpowiedziach diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf index 33e47664e5bb2..897409ec14d72 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf @@ -182,6 +182,11 @@ Mostrar os itens de namespaces não importados + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Mostrar os comentários nas Informações Rápidas diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf index df03f92d120d8..45357b2001345 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf @@ -182,6 +182,11 @@ Отображать элементы из неимпортированных пространств имен + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Показать заметки в кратких сведениях diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf index 7f5cc468adb37..9739714c45443 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf @@ -182,6 +182,11 @@ İçeri aktarılmayan ad alanlarındaki öğeleri göster + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info Hızlı Bilgi notlarını göster diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf index 511b5693de270..2506d0fd268c2 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf @@ -182,6 +182,11 @@ 显示 unimported 命名空间中的项 + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info 在快速信息中显示备注 diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf index 9db4dbd0a5950..b74d870657b7e 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf @@ -182,6 +182,11 @@ 顯示來自未匯入命名空間的項目 + + Show new snippet experience (experimental) + Show new snippet experience (experimental) + + Show remarks in Quick Info 在快速諮詢中顯示備註 diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveWindowCommandHandlerTestState.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveWindowCommandHandlerTestState.cs index 690622508aab3..aed09e95bd722 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveWindowCommandHandlerTestState.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveWindowCommandHandlerTestState.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.Interactive; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Composition; @@ -51,7 +52,7 @@ public InteractiveWindowCommandHandlerTestState(XElement workspaceElement) TestHost.Window, GetExportedValue(), GetExportedValue(), - GetExportedValue(), + GetExportedValue(), GetExportedValue()); } diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs index d518fac2ddfb9..eabf3d0f69453 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Utilities; +using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands { @@ -82,13 +83,13 @@ private async Task AssertResetInteractiveAsync( testHost.Evaluator.OnExecute += executeSubmission; var uiThreadOperationExecutor = workspace.GetService(); - var editorOptionsFactoryService = workspace.GetService(); - var editorOptions = editorOptionsFactoryService.GetOptions(testHost.Window.CurrentLanguageBuffer); + var editorOptionsService = workspace.GetService(); + var editorOptions = editorOptionsService.Factory.GetOptions(testHost.Window.CurrentLanguageBuffer); var newLineCharacter = editorOptions.GetNewLineCharacter(); var resetInteractive = new TestResetInteractive( uiThreadOperationExecutor, - editorOptionsFactoryService, + editorOptionsService, CreateReplReferenceCommand, CreateImport, buildSucceeds: buildSucceeds) diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestInteractiveCommandHandler.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestInteractiveCommandHandler.cs index 2b737fea9282b..d55cdd9b3e844 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestInteractiveCommandHandler.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestInteractiveCommandHandler.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Interactive; +using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; @@ -23,9 +24,9 @@ public TestInteractiveCommandHandler( IInteractiveWindow interactiveWindow, ISendToInteractiveSubmissionProvider sendToInteractiveSubmissionProvider, IContentTypeRegistryService contentTypeRegistryService, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, IEditorOperationsFactoryService editorOperationsFactoryService) - : base(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService) + : base(contentTypeRegistryService, editorOptionsService, editorOperationsFactoryService) { _interactiveWindow = interactiveWindow; _sendToInteractiveSubmissionProvider = sendToInteractiveSubmissionProvider; diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs index e167bc7c92725..a45a32d5ad88b 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs @@ -17,6 +17,7 @@ using Microsoft.VisualStudio.Utilities; using Microsoft.VisualStudio.Language.Intellisense.Utilities; using Microsoft.CodeAnalysis.Interactive; +using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands { @@ -46,11 +47,11 @@ internal class TestResetInteractive : ResetInteractive public TestResetInteractive( IUIThreadOperationExecutor uiThreadOperationExecutor, - IEditorOptionsFactoryService editorOptionsFactoryService, + EditorOptionsService editorOptionsService, Func createReference, Func createImport, bool buildSucceeds) - : base(editorOptionsFactoryService, createReference, createImport) + : base(editorOptionsService, createReference, createImport) { _uiThreadOperationExecutor = uiThreadOperationExecutor; _buildSucceeds = buildSucceeds; diff --git a/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs b/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs index f5b4079c4c2e4..e9bfa7068d082 100644 --- a/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs +++ b/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs @@ -130,10 +130,7 @@ private void AddDataPoint(DataPoint dataPoint) var versionedPoints = _dataPoints.GetOrAdd(dataPoint.Descriptor.ProjectGuid, _ => (version: VersionStamp.Default.ToString(), dataPoints: new HashSet())); versionedPoints.dataPoints.Add(dataPoint); - if (_pollingTask is null) - { - _pollingTask = Task.Run(PollForUpdatesAsync).ReportNonFatalErrorAsync(); - } + _pollingTask ??= Task.Run(PollForUpdatesAsync).ReportNonFatalErrorAsync(); } } diff --git a/src/VisualStudio/Core/Def/ChangeSignature/ChangeSignatureDialog.xaml.cs b/src/VisualStudio/Core/Def/ChangeSignature/ChangeSignatureDialog.xaml.cs index 4341b7a7c2458..d23a7cb8f6497 100644 --- a/src/VisualStudio/Core/Def/ChangeSignature/ChangeSignatureDialog.xaml.cs +++ b/src/VisualStudio/Core/Def/ChangeSignature/ChangeSignatureDialog.xaml.cs @@ -223,10 +223,7 @@ private void SetFocusToSelectedRow(bool focusRow) private static void FocusRow(DataGridRow row) { var cell = row.FindDescendant(); - if (cell != null) - { - cell.Focus(); - } + cell?.Focus(); } private void MoveSelectionUp_Click(object sender, EventArgs e) diff --git a/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs b/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs index 35c1d93e3c630..fa05bc5bb711c 100644 --- a/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs +++ b/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs @@ -49,10 +49,7 @@ internal void EnableCompletion() internal void DisableCompletion() { var featureService = _featureServiceFactory.GetOrCreate(WpfTextView); - if (_completionDisabledToken == null) - { - _completionDisabledToken = featureService.Disable(PredefinedEditorFeatureNames.Completion, this); - } + _completionDisabledToken ??= featureService.Disable(PredefinedEditorFeatureNames.Completion, this); } internal void SetNextFilter(IOleCommandTarget nextFilter) @@ -185,10 +182,7 @@ internal void SetContentType(bool install) public void Dispose() { - if (_completionDisabledToken != null) - { - _completionDisabledToken.Dispose(); - } + _completionDisabledToken?.Dispose(); RemoveContext(); } diff --git a/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerTextView.cs b/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerTextView.cs index 6c3dcc062e7a5..ebf421e3b85e8 100644 --- a/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerTextView.cs +++ b/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerTextView.cs @@ -306,10 +306,7 @@ public IMultiSelectionBroker MultiSelectionBroker { get { - if (_multiSelectionBroker == null) - { - _multiSelectionBroker = _innerTextView.GetMultiSelectionBroker(); - } + _multiSelectionBroker ??= _innerTextView.GetMultiSelectionBroker(); return _multiSelectionBroker; } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/Analyzers/ViewModel/SeverityViewModel.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/Analyzers/ViewModel/SeverityViewModel.cs index 47cea620a9a28..1decd6831739f 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/Analyzers/ViewModel/SeverityViewModel.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/Analyzers/ViewModel/SeverityViewModel.cs @@ -30,10 +30,7 @@ public string SelectedSeverityValue { get { - if (_selectedSeverityValue is null) - { - _selectedSeverityValue = Severities[_selectedSeverityIndex]; - } + _selectedSeverityValue ??= Severities[_selectedSeverityIndex]; return _selectedSeverityValue; } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleSeverityViewModel.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleSeverityViewModel.cs index eac12aec6f077..2c84d0d918a23 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleSeverityViewModel.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleSeverityViewModel.cs @@ -30,10 +30,7 @@ public string SelectedSeverityValue { get { - if (_selectedSeverityValue is null) - { - _selectedSeverityValue = Severities[_selectedSeverityIndex]; - } + _selectedSeverityValue ??= Severities[_selectedSeverityIndex]; return _selectedSeverityValue; } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleValueViewModel.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleValueViewModel.cs index 90218366fa822..1388049ea37b4 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleValueViewModel.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/CodeStyle/ViewModel/CodeStyleValueViewModel.cs @@ -18,10 +18,7 @@ public string SelectedValue { get { - if (_selectedValue is null) - { - _selectedValue = _setting.GetCurrentValue(); - } + _selectedValue ??= _setting.GetCurrentValue(); return _selectedValue; } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.SearchFilter.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.SearchFilter.cs index 8148321792dea..a0144dc37e66c 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.SearchFilter.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.SearchFilter.cs @@ -20,10 +20,7 @@ internal class SearchFilter : IEntryFilter public SearchFilter(IVsSearchQuery searchQuery, IWpfTableControl control) { _searchTokens = SearchUtilities.ExtractSearchTokens(searchQuery); - if (_searchTokens == null) - { - _searchTokens = Array.Empty(); - } + _searchTokens ??= Array.Empty(); var newVisibleColumns = new List(); foreach (var c in control.ColumnStates) diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs index a6e66e1b65fbf..6c103839f1cea 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorPane.cs @@ -264,10 +264,7 @@ protected override void OnClose() public int FDoIdle(uint grfidlef) { - if (_control is not null) - { - _control.SynchronizeSettings(); - } + _control?.SynchronizeSettings(); return S_OK; } diff --git a/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs b/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs index 5601ff2941860..3a0e7d68dafe6 100644 --- a/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs +++ b/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs @@ -49,19 +49,19 @@ public VisualStudioExtractClassOptionsService( _globalOptions = globalOptions; } - public async Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol selectedType, ISymbol? selectedMember, CancellationToken cancellationToken) + public async Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol selectedType, ImmutableArray selectedMembers, CancellationToken cancellationToken) { var notificationService = document.Project.Solution.Workspace.Services.GetRequiredService(); var membersInType = selectedType.GetMembers(). - WhereAsArray(member => MemberAndDestinationValidator.IsMemberValid(member)); + WhereAsArray(MemberAndDestinationValidator.IsMemberValid); var memberViewModels = membersInType .SelectAsArray(member => new PullMemberUpSymbolViewModel(member, _glyphService) { - // The member user selected will be checked at the beginning. - IsChecked = SymbolEquivalenceComparer.Instance.Equals(selectedMember, member), + // The member(s) user selected will be checked at the beginning. + IsChecked = selectedMembers.Any(SymbolEquivalenceComparer.Instance.Equals, member), MakeAbstract = false, IsMakeAbstractCheckable = !member.IsKind(SymbolKind.Field) && !member.IsAbstract, IsCheckable = true diff --git a/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs index 4eed8ccda52f1..c15c628af482d 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs @@ -80,12 +80,9 @@ protected override string GetProjectName() // so the user can know htat in the UI. lock (_projectFlavors) { - if (_cachedProjectName == null) - { - _cachedProjectName = _projectFlavors.Count < 2 + _cachedProjectName ??= _projectFlavors.Count < 2 ? _rawProjectName : $"{_rawProjectName} ({string.Join(", ", _projectFlavors)})"; - } return _cachedProjectName; } diff --git a/src/VisualStudio/Core/Def/Implementation/GCManager.cs b/src/VisualStudio/Core/Def/Implementation/GCManager.cs index bda25f03e8337..9a1e89a2cdd9a 100644 --- a/src/VisualStudio/Core/Def/Implementation/GCManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/GCManager.cs @@ -92,10 +92,7 @@ internal static void UseLowLatencyModeForProcessingUserInput() s_delay = currentDelay; } - if (currentDelay != null) - { - currentDelay.Reset(); - } + currentDelay?.Reset(); } private static void RestoreGCLatencyMode(GCLatencyMode originalMode) diff --git a/src/VisualStudio/Core/Def/Interactive/ScriptingOleCommandTarget.cs b/src/VisualStudio/Core/Def/Interactive/ScriptingOleCommandTarget.cs index c822ad3cba751..b0d4ae6b93da7 100644 --- a/src/VisualStudio/Core/Def/Interactive/ScriptingOleCommandTarget.cs +++ b/src/VisualStudio/Core/Def/Interactive/ScriptingOleCommandTarget.cs @@ -29,10 +29,7 @@ internal ScriptingOleCommandTarget( { var result = WpfTextView.GetBufferContainingCaret(contentType: ContentTypeNames.RoslynContentType); - if (result == null) - { - result = WpfTextView.GetBufferContainingCaret(contentType: PredefinedInteractiveCommandsContentTypes.InteractiveCommandContentTypeName); - } + result ??= WpfTextView.GetBufferContainingCaret(contentType: PredefinedInteractiveCommandsContentTypes.InteractiveCommandContentTypeName); return result; } diff --git a/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs b/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs index 3ac7d774c577b..1c1caeba5f9e1 100644 --- a/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs +++ b/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs @@ -26,6 +26,7 @@ using InteractiveHost::Microsoft.CodeAnalysis.Interactive; using Microsoft.VisualStudio.Utilities; using Microsoft.CodeAnalysis.Interactive; +using Microsoft.CodeAnalysis.Options; namespace Microsoft.VisualStudio.LanguageServices.Interactive { @@ -45,7 +46,7 @@ internal VsResetInteractive( IVsSolutionBuildManager buildManager, Func createReference, Func createImport) - : base(componentModel.GetService(), createReference, createImport) + : base(componentModel.GetService(), createReference, createImport) { _workspace = workspace; _dte = dte; diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index ac9cd810e355f..871f0f2839974 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -341,10 +341,7 @@ private void RestoreVsKeybindings() { AssertIsForeground(); - if (_uiShell == null) - { - _uiShell = _serviceProvider.GetServiceOnMainThread(); - } + _uiShell ??= _serviceProvider.GetServiceOnMainThread(); ErrorHandler.ThrowOnFailure(_uiShell.PostExecCommand( VSConstants.GUID_VSStandardCommandSet97, diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs index 6e69728f5a72d..a134eff7b5bf4 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs @@ -36,11 +36,6 @@ internal abstract class AbstractCreateServicesOnTextViewConnection : IWpfTextVie protected virtual Task InitializeServiceForOpenedDocumentAsync(Document document) => Task.CompletedTask; - protected virtual void OnSolutionRemoved() - { - return; - } - public AbstractCreateServicesOnTextViewConnection( VisualStudioWorkspace workspace, IGlobalOptionService globalOptions, @@ -56,7 +51,6 @@ public AbstractCreateServicesOnTextViewConnection( _languageName = languageName; Workspace.DocumentOpened += InitializeServiceOnDocumentOpened; - Workspace.WorkspaceChanged += OnWorkspaceChanged; } void IWpfTextViewConnectionListener.SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers) @@ -74,14 +68,6 @@ void IWpfTextViewConnectionListener.SubjectBuffersDisconnected(IWpfTextView text { } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) - { - if (e.Kind == WorkspaceChangeKind.SolutionRemoved) - { - OnSolutionRemoved(); - } - } - private void InitializeServiceOnDocumentOpened(object sender, DocumentEventArgs e) { if (e.Document.Project.Language != _languageName) diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs index f8f00dc6f2617..8ab437e505b25 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs @@ -56,7 +56,7 @@ private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, Cancella var documentSyntax = ParsedDocument.CreateSynchronously(document, cancellationToken); var text = documentSyntax.Text; var root = documentSyntax.Root; - var formattingOptions = document.GetSyntaxFormattingOptionsAsync(GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var formattingOptions = textBuffer.GetSyntaxFormattingOptions(EditorOptionsService, document.Project.LanguageServices, explicitFormat: true); var ts = selections.Single(); var start = text.Lines[ts.iStartLine].Start + ts.iStartIndex; diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs index 51aad65be847d..723ccaaeb799b 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs @@ -55,7 +55,7 @@ internal abstract partial class AbstractLanguageService(); + this.EditorOptionsService = this.Package.ComponentModel.GetService(); this.Workspace = this.Package.ComponentModel.GetService(); this.EditorAdaptersFactoryService = this.Package.ComponentModel.GetService(); this.HostDiagnosticUpdateSource = this.Package.ComponentModel.GetService(); @@ -168,10 +168,7 @@ protected virtual void Uninitialize() private void PrimeLanguageServiceComponentsOnBackground() { var formatter = this.Workspace.Services.GetLanguageServices(RoslynLanguageName).GetService(); - if (formatter != null) - { - formatter.GetDefaultFormattingRules(); - } + formatter?.GetDefaultFormattingRules(); } protected abstract string ContentTypeName { get; } diff --git a/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs b/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs index ee1f98ad229ab..55e3868ec5a28 100644 --- a/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs @@ -96,10 +96,7 @@ public bool ExecuteCommand(SyncClassViewCommandArgs args, CommandExecutionContex navInfo = libraryService.NavInfoFactory.CreateForSymbol(symbol, document.Project, semanticModel.Compilation, useExpandedHierarchy: true); } - if (navInfo == null) - { - navInfo = libraryService.NavInfoFactory.CreateForProject(document.Project); - } + navInfo ??= libraryService.NavInfoFactory.CreateForProject(document.Project); if (navInfo == null) { diff --git a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs index c8f16e5bcde5e..6f2d05805cf95 100644 --- a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs +++ b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs @@ -618,10 +618,7 @@ public ImmutableArray GetProjectListItems(Solution solution, str if (Helpers.IsObjectBrowser(listFlags)) { - if (assemblyIdentitySet == null) - { - assemblyIdentitySet = new HashSet(); - } + assemblyIdentitySet ??= new HashSet(); foreach (var reference in project.MetadataReferences) { diff --git a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs index 33625e63174fe..bf2c97dfe9551 100644 --- a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs +++ b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs @@ -67,10 +67,7 @@ internal abstract AbstractDescriptionBuilder CreateDescriptionBuilder( private AbstractListItemFactory GetListItemFactory() { - if (_listItemFactory == null) - { - _listItemFactory = CreateListItemFactory(); - } + _listItemFactory ??= CreateListItemFactory(); return _listItemFactory; } diff --git a/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialog.xaml b/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialog.xaml index f9fc94bc8df13..d656667ae1413 100644 --- a/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialog.xaml +++ b/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialog.xaml @@ -5,7 +5,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:sys="clr-namespace:System;assembly=mscorlib" - xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" + xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" + xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" xmlns:movestaticmembers="clr-namespace:Microsoft.VisualStudio.LanguageServices.Implementation.MoveStaticMembers" d:DataContext="{d:DesignInstance Type=movestaticmembers:MoveStaticMembersDialogViewModel}" x:Uid="MoveStaticMembersDialog" @@ -14,7 +15,7 @@ x:ClassModifier="internal" mc:Ignorable="d" WindowStartupLocation="CenterOwner" - Height="398" + Height="498" Width="500" MinHeight="298" MinWidth="210" @@ -36,22 +37,62 @@ - - - - - + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialogViewModel.cs b/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialogViewModel.cs index f02e161288486..3cd86f203ecc1 100644 --- a/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/MoveStaticMembers/MoveStaticMembersDialogViewModel.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Immutable; using System.ComponentModel; +using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Imaging.Interop; @@ -18,19 +20,18 @@ internal class MoveStaticMembersDialogViewModel : AbstractNotifyPropertyChanged private readonly ISyntaxFacts _syntaxFacts; - private readonly ImmutableArray _existingNames; - public MoveStaticMembersDialogViewModel( StaticMemberSelectionViewModel memberSelectionViewModel, string defaultType, - ImmutableArray existingNames, + ImmutableArray availableTypes, string prependedNamespace, ISyntaxFacts syntaxFacts) { MemberSelectionViewModel = memberSelectionViewModel; _syntaxFacts = syntaxFacts ?? throw new ArgumentNullException(nameof(syntaxFacts)); - _destinationName = defaultType; - _existingNames = existingNames; + _searchText = defaultType; + _destinationName = new TypeNameItem(defaultType); + AvailableTypes = availableTypes; PrependedNamespace = string.IsNullOrEmpty(prependedNamespace) ? prependedNamespace : prependedNamespace + "."; PropertyChanged += MoveMembersToTypeDialogViewModel_PropertyChanged; @@ -44,15 +45,35 @@ private void MoveMembersToTypeDialogViewModel_PropertyChanged(object sender, Pro case nameof(DestinationName): OnDestinationUpdated(); break; + + case nameof(SearchText): + OnSearchTextUpdated(); + break; } } + private void OnSearchTextUpdated() + { + var foundItem = AvailableTypes.FirstOrDefault(t => t.TypeName == SearchText); + if (foundItem is null) + { + DestinationName = new(PrependedNamespace + SearchText); + return; + } + + DestinationName = foundItem; + } + public void OnDestinationUpdated() { - // TODO change once we allow movement to existing types - var fullyQualifiedTypeName = PrependedNamespace + DestinationName; - var isNewType = !_existingNames.Contains(fullyQualifiedTypeName); - CanSubmit = isNewType && IsValidType(fullyQualifiedTypeName); + if (!_destinationName.IsNew) + { + CanSubmit = true; + ShowMessage = false; + return; + } + + CanSubmit = IsValidType(_destinationName.TypeName); if (CanSubmit) { @@ -89,12 +110,13 @@ private bool IsValidType(string typeName) } public string PrependedNamespace { get; } + public ImmutableArray AvailableTypes { get; } - private string _destinationName; - public string DestinationName + private TypeNameItem _destinationName; + public TypeNameItem DestinationName { get => _destinationName; - set => SetProperty(ref _destinationName, value); + private set => SetProperty(ref _destinationName, value); } private ImageMoniker _icon; @@ -124,5 +146,12 @@ public bool CanSubmit get => _canSubmit; set => SetProperty(ref _canSubmit, value); } + + private string _searchText; + public string SearchText + { + get => _searchText; + set => SetProperty(ref _searchText, value); + } } } diff --git a/src/VisualStudio/Core/Def/MoveStaticMembers/TypeNameItem.cs b/src/VisualStudio/Core/Def/MoveStaticMembers/TypeNameItem.cs new file mode 100644 index 0000000000000..664617257d26a --- /dev/null +++ b/src/VisualStudio/Core/Def/MoveStaticMembers/TypeNameItem.cs @@ -0,0 +1,67 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.MoveStaticMembers +{ + internal class TypeNameItem + { + public string TypeName { get; } + public INamedTypeSymbol? NamedType { get; } + public string DeclarationFilePath { get; } + public string DeclarationFileName { get; } + public bool IsFromHistory { get; } + public bool IsNew { get; } + + public TypeNameItem(bool isFromHistory, string declarationFile, INamedTypeSymbol type) + { + IsFromHistory = isFromHistory; + IsNew = false; + NamedType = type; + TypeName = type.ToDisplayString(); + DeclarationFileName = PathUtilities.GetFileName(declarationFile); + DeclarationFilePath = declarationFile; + } + + public TypeNameItem(string @typeName) + { + IsFromHistory = false; + IsNew = true; + TypeName = @typeName; + NamedType = null; + DeclarationFileName = string.Empty; + DeclarationFilePath = string.Empty; + } + + public override string ToString() => TypeName; + + public static int CompareTo(TypeNameItem x, TypeNameItem y) + { + // sort so that history is first, then type name, then file name + if (x.IsFromHistory ^ y.IsFromHistory) + { + // one is from history and the other isn't + return x.IsFromHistory ? -1 : 1; + } + // compare by each namespace/finally type + var xnames = x.TypeName.Split('.'); + var ynames = y.TypeName.Split('.'); + + for (var i = 0; i < Math.Min(xnames.Length, ynames.Length); i++) + { + var comp = xnames[i].CompareTo(ynames[i]); + if (comp != 0) + { + return comp; + } + } + + return x.DeclarationFileName.CompareTo(y.DeclarationFileName); + } + } +} diff --git a/src/VisualStudio/Core/Def/MoveStaticMembers/VisualStudioMoveStaticMembersOptionsService.cs b/src/VisualStudio/Core/Def/MoveStaticMembers/VisualStudioMoveStaticMembersOptionsService.cs index 4013ff86f1364..c9719e2194c52 100644 --- a/src/VisualStudio/Core/Def/MoveStaticMembers/VisualStudioMoveStaticMembersOptionsService.cs +++ b/src/VisualStudio/Core/Def/MoveStaticMembers/VisualStudioMoveStaticMembersOptionsService.cs @@ -3,7 +3,10 @@ // 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.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -28,6 +31,10 @@ internal class VisualStudioMoveStaticMembersOptionsService : IMoveStaticMembersO private readonly IGlyphService _glyphService; private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; + private const int HistorySize = 3; + + public readonly LinkedList History = new(); + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioMoveStaticMembersOptionsService( @@ -38,15 +45,21 @@ public VisualStudioMoveStaticMembersOptionsService( _uiThreadOperationExecutor = uiThreadOperationExecutor; } - public MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ISymbol? selectedNodeSymbol) + public MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ImmutableArray selectedNodeSymbols) { - var viewModel = GetViewModel(document, selectedType, selectedNodeSymbol, _glyphService, _uiThreadOperationExecutor); + var viewModel = GetViewModel(document, selectedType, selectedNodeSymbols, History, _glyphService, _uiThreadOperationExecutor); var dialog = new MoveStaticMembersDialog(viewModel); var result = dialog.ShowModal(); - return GenerateOptions(document.Project.Language, viewModel, result.GetValueOrDefault()); + if (result.GetValueOrDefault()) + { + UpdateHistory(viewModel); + return GenerateOptions(document.Project.Language, viewModel, result.GetValueOrDefault()); + } + + return MoveStaticMembersOptions.Cancelled; } // internal for testing purposes @@ -55,12 +68,23 @@ internal static MoveStaticMembersOptions GenerateOptions(string language, MoveSt if (dialogResult) { // if the destination name contains extra namespaces, we want the last one as that is the real type name - var typeName = viewModel.DestinationName.Split('.').Last(); + var typeName = viewModel.DestinationName.TypeName.Split('.').Last(); var newFileName = Path.ChangeExtension(typeName, language == LanguageNames.CSharp ? ".cs" : ".vb"); + var selectedMembers = viewModel.MemberSelectionViewModel.CheckedMembers.SelectAsArray(vm => vm.Symbol); + + if (viewModel.DestinationName.IsNew) + { + return new MoveStaticMembersOptions( + newFileName, + viewModel.DestinationName.TypeName, + selectedMembers); + } + + RoslynDebug.AssertNotNull(viewModel.DestinationName.NamedType); + return new MoveStaticMembersOptions( - newFileName, - viewModel.PrependedNamespace + viewModel.DestinationName, - viewModel.MemberSelectionViewModel.CheckedMembers.SelectAsArray(vm => vm.Symbol)); + viewModel.DestinationName.NamedType, + selectedMembers); } return MoveStaticMembersOptions.Cancelled; @@ -70,7 +94,8 @@ internal static MoveStaticMembersOptions GenerateOptions(string language, MoveSt internal static MoveStaticMembersDialogViewModel GetViewModel( Document document, INamedTypeSymbol selectedType, - ISymbol? selectedNodeSymbol, + ImmutableArray selectedNodeSymbols, + LinkedList history, IGlyphService? glyphService, IUIThreadOperationExecutor uiThreadOperationExecutor) { @@ -81,14 +106,15 @@ internal static MoveStaticMembersDialogViewModel GetViewModel( .SelectAsArray(member => new SymbolViewModel(member, glyphService) { - // The member user selected will be checked at the beginning. - IsChecked = SymbolEquivalenceComparer.Instance.Equals(selectedNodeSymbol, member), + // The member(s) user selected will be checked at the beginning. + IsChecked = selectedNodeSymbols.Any(SymbolEquivalenceComparer.Instance.Equals, member), }); using var cancellationTokenSource = new CancellationTokenSource(); var memberToDependentsMap = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, document.Project, cancellationTokenSource.Token); - var existingTypeNames = selectedType.ContainingNamespace.GetAllTypes(cancellationTokenSource.Token).SelectAsArray(t => t.ToDisplayString()); + var existingTypes = selectedType.ContainingNamespace.GetAllTypes(cancellationTokenSource.Token).ToImmutableArray(); + var existingTypeNames = existingTypes.SelectAsArray(t => t.ToDisplayString()); var candidateName = selectedType.Name + "Helpers"; var defaultTypeName = NameGenerator.GenerateUniqueName(candidateName, name => !existingTypeNames.Contains(name)); @@ -101,11 +127,67 @@ internal static MoveStaticMembersDialogViewModel GetViewModel( memberViewModels, memberToDependentsMap); - return new MoveStaticMembersDialogViewModel(selectMembersViewModel, + var availableTypeNames = MakeTypeNameItems( + selectedType.ContainingNamespace, + selectedType, + document, + history, + cancellationTokenSource.Token); + + return new MoveStaticMembersDialogViewModel( + selectMembersViewModel, defaultTypeName, - existingTypeNames, + availableTypeNames, containingNamespaceDisplay, document.GetRequiredLanguageService()); } + + private void UpdateHistory(MoveStaticMembersDialogViewModel viewModel) + { + if (viewModel.DestinationName.IsNew || viewModel.DestinationName.IsFromHistory) + { + // if we create a new destination or already have it in the history, + // we don't need to update our history list. + return; + } + + History.AddFirst(viewModel.DestinationName.NamedType!); + if (History.Count > HistorySize) + { + History.RemoveLast(); + } + } + + private static string GetFile(Location loc) => loc.SourceTree!.FilePath; + + /// + /// Construct all the type names declared in the project, + /// + private static ImmutableArray MakeTypeNameItems( + INamespaceSymbol currentNamespace, + INamedTypeSymbol currentType, + Document currentDocument, + LinkedList history, + CancellationToken cancellationToken) + { + return currentNamespace.GetAllTypes(cancellationToken) + // only take symbols that are the same kind of type (class, module) + // and remove non-static types only when the current type is static + .Where(t => t.TypeKind == currentType.TypeKind && (t.IsStaticType() || !currentType.IsStaticType())) + .SelectMany(t => + { + // for partially declared classes, we may want multiple entries for a single type. + // filter to those actually in a real file, and that is not our current location. + return t.Locations + .Where(l => l.IsInSource && + (currentType.Name != t.Name || GetFile(l) != currentDocument.FilePath)) + .Select(l => new TypeNameItem( + history.Contains(t), + GetFile(l), + t)); + }) + .ToImmutableArrayOrEmpty() + .Sort(comparison: TypeNameItem.CompareTo); + } } } diff --git a/src/VisualStudio/Core/Def/Notification/VSNotificationServiceFactory.cs b/src/VisualStudio/Core/Def/Notification/VSNotificationServiceFactory.cs index 312e6db4fefe8..ab6724b3da840 100644 --- a/src/VisualStudio/Core/Def/Notification/VSNotificationServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Notification/VSNotificationServiceFactory.cs @@ -34,10 +34,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { lock (s_gate) { - if (s_singleton == null) - { - s_singleton = new VSDialogService(_uiShellService); - } + s_singleton ??= new VSDialogService(_uiShellService); } return s_singleton; diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 97ff1fa42afb2..22f8f8643d08b 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -144,8 +144,7 @@ public ImmutableArray TryGetPackageSources() Task> localPackageSourcesTask; lock (_gate) { - if (_packageSourcesTask is null) - _packageSourcesTask = Task.Run(() => GetPackageSourcesAsync(), this.DisposalToken); + _packageSourcesTask ??= Task.Run(() => GetPackageSourcesAsync(), this.DisposalToken); localPackageSourcesTask = _packageSourcesTask; } diff --git a/src/VisualStudio/Core/Def/PickMembers/PickMembersDialog.xaml b/src/VisualStudio/Core/Def/PickMembers/PickMembersDialog.xaml index ea6dce3a10f54..a64ccc738c020 100644 --- a/src/VisualStudio/Core/Def/PickMembers/PickMembersDialog.xaml +++ b/src/VisualStudio/Core/Def/PickMembers/PickMembersDialog.xaml @@ -11,6 +11,8 @@ xmlns:u="clr-namespace:Microsoft.VisualStudio.LanguageServices.Implementation.Utilities" xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" + xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" + xmlns:platformimaging="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging" mc:Ignorable="d" d:DesignHeight="380" d:DesignWidth="460" Height="380" Width="460" @@ -21,12 +23,17 @@ ResizeMode="CanResizeWithGrip" ShowInTaskbar="False" HasDialogFrame="True" - WindowStartupLocation="CenterOwner"> + WindowStartupLocation="CenterOwner" + vs:ThemedDialogStyleLoader.UseDefaultThemedDialogStyles="True" + Background="{DynamicResource {x:Static vs:ThemedDialogColors.WindowPanelBrushKey}}" + platformimaging:ImageThemingUtilities.ImageBackgroundColor="{StaticResource {x:Static vsshell:VsColors.ToolWindowBackgroundKey}}"> + +