diff --git a/Compilers.sln b/Compilers.sln
index c03b89de1d83f..d047ebb257b2c 100644
--- a/Compilers.sln
+++ b/Compilers.sln
@@ -168,7 +168,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeStyleConfigFileGenerato
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Collections", "src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.shproj", "{E919DD77-34F8-4F57-8058-4D3FF4C2B241}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebuild", "src\Compilers\Core\Rebuild\Rebuild.csproj", "{321F9FED-AACC-42CB-93E5-541D79E099E8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebuild", "src\Compilers\Core\Rebuild\Microsoft.CodeAnalysis.Rebuild.csproj", "{321F9FED-AACC-42CB-93E5-541D79E099E8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebuild.UnitTests", "src\Compilers\Core\RebuildTest\Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj", "{FDBFBB64-5980-41C2-9E3E-FB8E2F700A5C}"
EndProject
diff --git a/Compilers.slnf b/Compilers.slnf
index f03772c2261d0..b1ac6a6a8ad6f 100644
--- a/Compilers.slnf
+++ b/Compilers.slnf
@@ -31,7 +31,7 @@
"src\\Compilers\\Core\\MSBuildTask\\Microsoft.Build.Tasks.CodeAnalysis.csproj",
"src\\Compilers\\Core\\Portable\\Microsoft.CodeAnalysis.csproj",
"src\\Compilers\\Core\\RebuildTest\\Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj",
- "src\\Compilers\\Core\\Rebuild\\Rebuild.csproj",
+ "src\\Compilers\\Core\\Rebuild\\Microsoft.CodeAnalysis.Rebuild.csproj",
"src\\Compilers\\Extension\\Roslyn.Compilers.Extension.csproj",
"src\\Compilers\\Server\\VBCSCompilerTests\\VBCSCompiler.UnitTests.csproj",
"src\\Compilers\\Server\\VBCSCompiler\\VBCSCompiler.csproj",
diff --git a/Roslyn.sln b/Roslyn.sln
index db3fb77dc8658..303f630b773cb 100644
--- a/Roslyn.sln
+++ b/Roslyn.sln
@@ -483,7 +483,7 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Coll
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Collections.Package", "src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.Package.csproj", "{0C2E1633-1462-4712-88F4-A0C945BAD3A8}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebuild", "src\Compilers\Core\Rebuild\Rebuild.csproj", "{B7D29559-4360-434A-B9B9-2C0612287999}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebuild", "src\Compilers\Core\Rebuild\Microsoft.CodeAnalysis.Rebuild.csproj", "{B7D29559-4360-434A-B9B9-2C0612287999}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebuild.UnitTests", "src\Compilers\Core\RebuildTest\Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj", "{21B49277-E55A-45EF-8818-744BCD6CB732}"
EndProject
diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml
index b1bf1d64f9570..79d14e429b0cb 100644
--- a/azure-pipelines-official.yml
+++ b/azure-pipelines-official.yml
@@ -48,11 +48,11 @@ stages:
- job: OfficialBuild
displayName: Official Build
timeoutInMinutes: 360
- # Conditionally set build pool so we can share this YAML when building with different pipeline (devdiv vs dnceng)
- pool:
+ # Conditionally set build pool so we can share this YAML when building with different pipeline (devdiv vs dnceng)
+ pool:
${{ if eq(variables['System.TeamProject'], 'DevDiv') }}:
name: VSEngSS-MicroBuild2017
- demands:
+ demands:
- msbuild
- visualstudio
- DotNetFramework
@@ -60,7 +60,7 @@ stages:
name: NetCoreInternal-Pool
queue: BuildPool.Windows.10.Amd64.VS2019.Pre
- steps:
+ steps:
# Make sure our two pipelines generate builds with distinct build numbers to avoid confliction.
# i.e. DevDiv builds will have even rev# whereas dnceng builds will be odd.
- task: PowerShell@2
@@ -82,7 +82,7 @@ stages:
type: 'Build'
tags: 'OfficialBuild'
condition: and(succeeded(), endsWith(variables['SourceBranchName'], '-vs-deps'), eq(variables['PRNumber'], 'default'))
-
+
- task: tagBuildOrRelease@0
displayName: Tag PR validation build
inputs:
@@ -152,13 +152,13 @@ stages:
displayName: Build
inputs:
filePath: eng/build.ps1
- arguments: -ci
+ arguments: -ci
-restore
-build
-pack
-sign
-publish
- -binaryLog
+ -binaryLog
-configuration $(BuildConfiguration)
-officialBuildId $(Build.BuildNumber)
-officialSkipTests $(SkipTests)
@@ -249,7 +249,7 @@ stages:
# Publish insertion packages to CoreXT store.
- task: NuGetCommand@2
- displayName: Publish CoreXT Packages
+ displayName: Publish CoreXT Packages
inputs:
command: push
packagesToPush: '$(Build.SourcesDirectory)\artifacts\VSSetup\$(BuildConfiguration)\DevDivPackages\**\*.nupkg'
@@ -270,7 +270,7 @@ stages:
ArtifactName: 'VSSetup'
condition: succeeded()
- # Publish our NuPkgs as an artifact. The name of this artifact must be PackageArtifacts as the
+ # Publish our NuPkgs as an artifact. The name of this artifact must be PackageArtifacts as the
# arcade templates depend on the name.
- task: PublishBuildArtifacts@1
displayName: Publish Artifact Packages
@@ -295,7 +295,7 @@ stages:
- template: /eng/common/templates/job/publish-build-assets.yml
parameters:
publishUsingPipelines: true
- dependsOn:
+ dependsOn:
- OfficialBuild
${{ if eq(variables['System.TeamProject'], 'DevDiv') }}:
queue:
@@ -304,7 +304,7 @@ stages:
pool:
vmImage: vs2017-win2016
-# We need to skip post-build stages for PR validation build, but it can only be identified by
+# We need to skip post-build stages for PR validation build, but it can only be identified by
# the runtime variable 'PRNumber', thus this dummy stage. Also the dummy job is required
# otherwise AzDO would just repeat jobs from previous stage.
- stage: SetValidateDependency
@@ -324,8 +324,8 @@ stages:
# https://github.com/dotnet/arcade/issues/2871 is resolved.
enableSymbolValidation: false
enableSourceLinkValidation: false
- # It's important that post-build stages are depends on 'SetValidateDependency' stage instead of 'build',
- # since we don't want to publish validation build.
+ # It's important that post-build stages are depends on 'SetValidateDependency' stage instead of 'build',
+ # since we don't want to publish validation build.
validateDependsOn:
- SetValidateDependency
dependsOn:
diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md
index 53e399e2dd4c0..579fd1e764325 100644
--- a/docs/Language Feature Status.md
+++ b/docs/Language Feature Status.md
@@ -12,9 +12,11 @@ efforts behind them.
| ------- | ------ | ----- | ---------- | -------- | --------- |
| [Record structs](https://github.com/dotnet/csharplang/issues/4334) | [record-structs](https://github.com/dotnet/roslyn/tree/features/record-structs) | [In Progress](https://github.com/dotnet/roslyn/issues/51199) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) |
| [Global Using Directive](https://github.com/dotnet/csharplang/issues/3428) | [GlobalUsingDirective](https://github.com/dotnet/roslyn/tree/features/GlobalUsingDirective) | [In Progress](https://github.com/dotnet/roslyn/issues/51307) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | [AlekseyTs](https://github.com/AlekseyTs) |
+| [Static Abstract Members In Interfaces](https://github.com/dotnet/csharplang/issues/4436) | [StaticAbstractMembersInInterfaces](https://github.com/dotnet/roslyn/tree/features/StaticAbstractMembersInInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/52221) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) |
| [File-scoped namespace](https://github.com/dotnet/csharplang/issues/137) | [FileScopedNamespaces](https://github.com/dotnet/roslyn/tree/features/FileScopedNamespaces) | In Progress | [chsienki](https://github.com/chsienki) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) |
| [Interpolated string improvements](https://github.com/dotnet/csharplang/issues/4487) | [interpolated-string](https://github.com/dotnet/roslyn/tree/features/interpolated-string) | [In Progress](https://github.com/dotnet/roslyn/issues/51499) | [333fred](https://github.com/333fred) | [AlekseyTs](https://github.com/AlekseyTs), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) |
-| [Parameterless struct constructors](https://github.com/dotnet/csharplang/issues/99) | TBD | In Progress | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [jcouv](https://github.com/jouv) |
+| [Parameterless struct constructors](https://github.com/dotnet/csharplang/issues/99) | [struct-ctors](https://github.com/dotnet/roslyn/tree/features/struct-ctors) | [In Progress](https://github.com/dotnet/roslyn/issues/51698) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [jcouv](https://github.com/jouv) |
+| [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/lambda-improvements.md) | [lambdas](https://github.com/dotnet/roslyn/tree/features/lambdas) | [In Progress](https://github.com/dotnet/roslyn/issues/52192) | [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) |
| [nameof(parameter)](https://github.com/dotnet/csharplang/issues/373) | main | [In Progress](https://github.com/dotnet/roslyn/issues/40524) | [jcouv](https://github.com/jcouv) | TBD | [jcouv](https://github.com/jcouv) |
| [Improved Definite Assignment](https://github.com/dotnet/csharplang/issues/4465) | [improved-definite-assignment](https://github.com/dotnet/roslyn/tree/features/improved-definite-assignment) | [In Progress](https://github.com/dotnet/roslyn/issues/51463) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) |
| [Relax ordering of `ref` and `partial` modifiers](https://github.com/dotnet/csharplang/issues/946) | [ref-partial](https://github.com/dotnet/roslyn/tree/features/ref-partial) | In Progress | [alrz](https://github.com/alrz) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) |
diff --git a/docs/compilers/Deterministic Inputs.md b/docs/compilers/Deterministic Inputs.md
index 2d7c1bf336432..c865ff6ffad0f 100644
--- a/docs/compilers/Deterministic Inputs.md
+++ b/docs/compilers/Deterministic Inputs.md
@@ -1,13 +1,12 @@
Deterministic Inputs
====================
-We are aiming to make the compilers ultimately deterministic (https://github.com/dotnet/roslyn/issues/372). What that means is that the "same inputs" will cause the compilers to produce the "same outputs".
+The C# and VB compilers are fully deterministic when the `/deterministic` option is specified (this is the default in the .NET SDK). This means that the "same inputs" will cause the compilers to produce the "same outputs" byte for byte.
The following are considered inputs to the compiler for the purpose of determinism:
-- The sequence of command-line parameters
-- The contents of the compiler's `.rsp` response file.
-- The precise version of the compiler used, and its referenced assemblies
+- The sequence of command-line parameters (order is important)
+- The precise version of the compiler used and the files included in its deployment: reference assemblies, rsp, etc ...
- Current full directory path (you can reduce this to a relative path; see https://github.com/dotnet/roslyn/issues/949)
- (Binary) contents of all files explicitly passed to the compiler, directly or indirectly, including
- source files
@@ -17,15 +16,17 @@ The following are considered inputs to the compiler for the purpose of determini
- the strong name key file
- `@` response files
- Analyzers
+ - Generators
- Rulesets
- - "additional files" that may be used by analyzers
-- The current culture (for the language in which diagnostics and exception messages are produced).
+ - "additional files" that may be used by analyzers and generators
+- The current culture if `/preferreduilang` is not specified (for the language in which diagnostics and exception messages are produced).
- The current OS code page if `/codepage` is not specified and any of the input source files do not have BOM and are not UTF-8 encoded.
- The existence, non-existence, and contents of files on the compiler's search paths (specified, e.g. by `/lib` or `/recurse`)
- The CLR platform on which the compiler is run:
- The result of `double` arithmetic performed for constant-folding may use excess precision on some platforms.
- The compiler uses Unicode tables provided by the platform.
- The version of the zlib library that the CLR uses to implement compression (when `/embed` or `/debug:embedded` is used).
-- The value of `%LIBPATH%`, as it can affect analyzer dependency loading.
+- The value of `%LIBPATH%`, as it can affect reference discovery if not fully qualified and how the runtime handles analyzer / generator dependency loading.
+- The full path of source files although `/pathmap` can be used to normalize this between compiles of the same code in different root directories.
At the moment the compiler also depends on the time of day and random numbers for GUIDs, so it is not deterministic unless you specify `/deterministic`.
diff --git a/docs/wiki/Manual-Testing.md b/docs/wiki/Manual-Testing.md
index 260f92311e664..05ab1927ecd8f 100644
--- a/docs/wiki/Manual-Testing.md
+++ b/docs/wiki/Manual-Testing.md
@@ -66,7 +66,7 @@ When doing a test pass, copy this page and consider using these status indicator
| **Typing** | :fast_forward: **General Typing**
- Type and paste new constructs
- Nothing interferes with verbatim typing | | | |
| | :mag: :fast_forward: **Completion**
- Typing new keyword/construct names
- Dotting off of new constructs
- Matching part of the identifier is highlighted (including word prefix matches) [Visual Studio 2015 Update 1]
- Target type preselection [Visual Studio 2017]
IntelliSense filtering [Visual Studio 2017] | | | |
| | :fast_forward: **Formatting**
- Spacing in and around new constructs
- Spacing options
- Format Document command
`Tools > Options` settings should be respected | | | |
-| | :fast_forward: **Automatic Brace Completion** (*C# only*)
- Auto-insert close brace
- Shift+Enter commit of IntelliSense and any pending brace completion sessions (Known issue: https://github.com/dotnet/roslyn/issues/18065) | | | N/A |
+| | :fast_forward: **Automatic Brace Completion** (*C# only*)
- Auto-insert close brace
- Shift+Enter commit of IntelliSense and any pending brace completion sessions (Currently C# only: https://github.com/dotnet/roslyn/issues/18065) | | | N/A |
| | :fast_forward: **Indentation**
- Typing `Enter` in an unfinished statement indents the next line | | | |
| **Navigating** | :mag: :fast_forward: **Go To Definition**
- F12 from callsites to definition
- Ctrl+click [Visual Studio 2017 version 15.4] | | | |
| | :fast_forward: **Go To Implementation**
- Ctrl+F12 to jump from virtual members to their implementations
- Jump from inheritable types to their implementations | | | N/A |
diff --git a/eng/Versions.props b/eng/Versions.props
index da6f91a9cf921..9c5fe36c3dbf7 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -8,7 +8,7 @@
3
10
0
- 2
+ 3
$(MajorVersion).$(MinorVersion).$(PatchVersion)
-
+
@@ -1440,7 +1440,7 @@
-
+
-
+
@@ -1964,7 +1964,7 @@
-
+
@@ -2130,7 +2130,7 @@
-
+
@@ -2146,7 +2146,7 @@
-
+
diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
index 502d910ed047f..8116475713893 100644
--- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
+++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
@@ -2360,7 +2360,7 @@ internal void ReportUnusedImports(SyntaxTree? filterTree, BindingDiagnosticBag d
else if (info.Kind == SyntaxKind.ExternAliasDirective)
{
// Record targets of used extern aliases
- var node = info.Tree.GetRoot().FindToken(info.Span.Start, findInsideTrivia: false).
+ var node = info.Tree.GetRoot(cancellationToken).FindToken(info.Span.Start, findInsideTrivia: false).
Parent!.FirstAncestorOrSelf();
if (node is object && GetExternAliasTarget(node.Identifier.ValueText, out NamespaceSymbol target))
diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs
index 86faca93ca01f..61f0a48e49946 100644
--- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs
+++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs
@@ -1260,7 +1260,7 @@ private SynthesizedRecordConstructor TryGetSynthesizedRecordConstructor(RecordDe
NamedTypeSymbol recordType = GetDeclaredType(node);
var symbol = recordType.GetMembersUnordered().OfType().SingleOrDefault();
- if (symbol?.GetSyntax() != node)
+ if (symbol?.SyntaxRef.SyntaxTree != node.SyntaxTree || symbol.GetSyntax() != node)
{
return null;
}
diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/CSharpDataFlowAnalysis.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/CSharpDataFlowAnalysis.cs
index 96f70fd620a3d..bf24860d0dfba 100644
--- a/src/Compilers/CSharp/Portable/FlowAnalysis/CSharpDataFlowAnalysis.cs
+++ b/src/Compilers/CSharp/Portable/FlowAnalysis/CSharpDataFlowAnalysis.cs
@@ -371,7 +371,7 @@ private HashSet UnassignedVariableAddressOfSyntaxes
}
///
- /// Returns true iff analysis was successful. Analysis can fail if the region does not properly span a single expression,
+ /// Returns true if and only if analysis was successful. Analysis can fail if the region does not properly span a single expression,
/// a single statement, or a contiguous series of statements within the enclosing block.
///
public sealed override bool Succeeded
diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs
index 226af335a1d09..a467bdf6efbc9 100644
--- a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs
+++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs
@@ -133,7 +133,7 @@ public override ImmutableArray ReturnStatements
}
///
- /// Returns true iff analysis was successful. Analysis can fail if the region does not properly span a single expression,
+ /// Returns true if and only if analysis was successful. Analysis can fail if the region does not properly span a single expression,
/// a single statement, or a contiguous series of statements within the enclosing block.
///
public sealed override bool Succeeded
diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs
index 09d63db09f1d6..d7da9cca73740 100644
--- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs
+++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs
@@ -509,7 +509,7 @@ public BoundAwaitableValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSym
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public uint ValEscape { get; }
[DebuggerStepThrough]
@@ -853,7 +853,7 @@ public BoundNamespaceExpression(SyntaxNode syntax, NamespaceSymbol namespaceSymb
}
- public new TypeSymbol Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public NamespaceSymbol NamespaceSymbol { get; }
@@ -1018,7 +1018,7 @@ public BoundUnconvertedAddressOfOperator(SyntaxNode syntax, BoundMethodGroup ope
public BoundMethodGroup Operand { get; }
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitUnconvertedAddressOfOperator(this);
@@ -2325,7 +2325,7 @@ public BoundDefaultLiteral(SyntaxNode syntax)
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitDefaultLiteral(this);
@@ -2613,7 +2613,7 @@ public BoundArgListOperator(SyntaxNode syntax, ImmutableArray a
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public ImmutableArray Arguments { get; }
@@ -4047,7 +4047,7 @@ public BoundBaseReference(SyntaxNode syntax, TypeSymbol? type)
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitBaseReference(this);
@@ -5204,7 +5204,7 @@ protected BoundMethodOrPropertyGroup(BoundKind kind, SyntaxNode syntax, BoundExp
}
- public new TypeSymbol Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public BoundExpression? ReceiverOpt { get; }
@@ -5821,7 +5821,7 @@ public BoundUnconvertedObjectCreationExpression(SyntaxNode syntax, ImmutableArra
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public ImmutableArray Arguments { get; }
@@ -5940,7 +5940,7 @@ public BoundTupleLiteral(SyntaxNode syntax, ImmutableArray argu
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitTupleLiteral(this);
@@ -6563,7 +6563,7 @@ public BoundArrayInitialization(SyntaxNode syntax, ImmutableArray base.Type!;
+ public new TypeSymbol? Type => base.Type;
public ImmutableArray Initializers { get; }
[DebuggerStepThrough]
@@ -6992,7 +6992,7 @@ public BoundLambda(SyntaxNode syntax, UnboundLambda unboundLambda, LambdaSymbol
public LambdaSymbol Symbol { get; }
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public BoundBlock Body { get; }
@@ -7037,7 +7037,7 @@ public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, Boolean withDep
}
- public new TypeSymbol Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public UnboundLambdaState Data { get; }
@@ -7717,7 +7717,7 @@ public BoundDiscardExpression(SyntaxNode syntax, TypeSymbol? type)
}
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitDiscardExpression(this);
@@ -7774,7 +7774,7 @@ protected VariablePendingInference(BoundKind kind, SyntaxNode syntax, Symbol var
}
- public new TypeSymbol Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public Symbol VariableSymbol { get; }
@@ -7844,7 +7844,7 @@ public OutDeconstructVarPendingInference(SyntaxNode syntax)
}
- public new TypeSymbol Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitOutDeconstructVarPendingInference(this);
@@ -7937,7 +7937,7 @@ public BoundExpressionWithNullability(SyntaxNode syntax, BoundExpression express
public BoundExpression Expression { get; }
- public new TypeSymbol? Type => base.Type!;
+ public new TypeSymbol? Type => base.Type;
public NullableAnnotation NullableAnnotation { get; }
[DebuggerStepThrough]
diff --git a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj
index b952c21041c66..ae5c7ac879933 100644
--- a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj
+++ b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj
@@ -62,6 +62,7 @@
+
diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs
index 00cd1b4219ae4..7ffdfa34258ac 100644
--- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs
@@ -131,7 +131,7 @@ internal bool IsMarshalAsObject
/// Returns true if the parameter is semantically optional.
///
///
- /// True iff the parameter has a default argument syntax,
+ /// True if and only if the parameter has a default argument syntax,
/// or the parameter is not a params-array and Optional metadata flag is set.
///
public bool IsOptional
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs
index a223121680f94..95c8b305768ac 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs
@@ -116,7 +116,11 @@ private DeclarationModifiers MakeModifiers(SyntaxTokenList modifiers, MethodKind
if (methodKind == MethodKind.StaticConstructor)
{
- if ((mods & DeclarationModifiers.AccessibilityMask) != 0)
+ // Don't report ERR_StaticConstructorWithAccessModifiers if the ctor symbol name doesn't match the containing type name.
+ // This avoids extra unnecessary errors.
+ // There will already be a diagnostic saying Method must have a return type.
+ if ((mods & DeclarationModifiers.AccessibilityMask) != 0 &&
+ ContainingType.Name == ((ConstructorDeclarationSyntax)this.SyntaxNode).Identifier.ValueText)
{
diagnostics.Add(ErrorCode.ERR_StaticConstructorWithAccessModifiers, location, this);
mods = mods & ~DeclarationModifiers.AccessibilityMask;
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs
index c4bbe7916d425..a0e05f31f5354 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs
@@ -60,7 +60,11 @@ protected sealed override void MethodChecks(BindingDiagnosticBag diagnostics)
_lazyReturnType = TypeWithAnnotations.Create(bodyBinder.GetSpecialType(SpecialType.System_Void, diagnostics, syntax));
var location = this.Locations[0];
- if (MethodKind == MethodKind.StaticConstructor && (_lazyParameters.Length != 0))
+ // Don't report ERR_StaticConstParam if the ctor symbol name doesn't match the containing type name.
+ // This avoids extra unnecessary errors.
+ // There will already be a diagnostic saying Method must have a return type.
+ if (MethodKind == MethodKind.StaticConstructor && (_lazyParameters.Length != 0) &&
+ ContainingType.Name == ((ConstructorDeclarationSyntax)this.SyntaxNode).Identifier.ValueText)
{
diagnostics.Add(ErrorCode.ERR_StaticConstParam, location, this);
}
diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs
index 46b26c57c47ef..d35c1fff5ff08 100644
--- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs
+++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs
@@ -434,6 +434,11 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next)
}
}
+ if (token.IsKind(SyntaxKind.GreaterThanToken) && token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList))
+ {
+ return true;
+ }
+
if (token.IsKind(SyntaxKind.CommaToken) &&
!next.IsKind(SyntaxKind.CommaToken) &&
!token.Parent.IsKind(SyntaxKind.EnumDeclaration))
@@ -456,7 +461,8 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next)
if (token.IsKind(SyntaxKind.ColonToken))
{
- return !token.Parent.IsKind(SyntaxKind.InterpolationFormatClause);
+ return !token.Parent.IsKind(SyntaxKind.InterpolationFormatClause) &&
+ !token.Parent.IsKind(SyntaxKind.XmlPrefix);
}
if (next.IsKind(SyntaxKind.ColonToken))
@@ -485,9 +491,108 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next)
return true;
}
- if (token.IsKind(SyntaxKind.EqualsToken) || next.IsKind(SyntaxKind.EqualsToken))
+ if (token.IsKind(SyntaxKind.EqualsToken))
{
- return true;
+ return !token.Parent.IsKind(SyntaxKind.XmlTextAttribute);
+ }
+
+ if (next.IsKind(SyntaxKind.EqualsToken))
+ {
+ return !next.Parent.IsKind(SyntaxKind.XmlTextAttribute);
+ }
+
+ // Rules for function pointer below are taken from:
+ // https://github.com/dotnet/roslyn/blob/1cca63b5d8ea170f8d8e88e1574aa3ebe354c23b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs#L321-L413
+ if (token.Parent.IsKind(SyntaxKind.FunctionPointerType))
+ {
+ // No spacing between delegate and *
+ if (next.IsKind(SyntaxKind.AsteriskToken) && token.IsKind(SyntaxKind.DelegateKeyword))
+ {
+ return false;
+ }
+
+ // Force a space between * and the calling convention
+ if (token.IsKind(SyntaxKind.AsteriskToken) && next.Parent.IsKind(SyntaxKind.FunctionPointerCallingConvention))
+ {
+ switch (next.Kind())
+ {
+ case SyntaxKind.IdentifierToken:
+ case SyntaxKind.ManagedKeyword:
+ case SyntaxKind.UnmanagedKeyword:
+ return true;
+ }
+ }
+ }
+
+ if (next.Parent.IsKind(SyntaxKind.FunctionPointerParameterList) && next.IsKind(SyntaxKind.LessThanToken))
+ {
+ switch (token.Kind())
+ {
+ // No spacing between the * and < tokens if there is no calling convention
+ case SyntaxKind.AsteriskToken:
+ // No spacing between the calling convention and opening angle bracket of function pointer types:
+ // delegate* managed<
+ case SyntaxKind.ManagedKeyword:
+ case SyntaxKind.UnmanagedKeyword:
+ // No spacing between the calling convention specifier and the opening angle
+ // delegate* unmanaged[Cdecl]<
+ case SyntaxKind.CloseBracketToken when token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList):
+ return false;
+ }
+ }
+
+ // No space between unmanaged and the [
+ // delegate* unmanaged[
+ if (token.Parent.IsKind(SyntaxKind.FunctionPointerCallingConvention) && next.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList) &&
+ next.IsKind(SyntaxKind.OpenBracketToken))
+ {
+ return false;
+ }
+
+ // Function pointer calling convention adjustments
+ if (next.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList) && token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList))
+ {
+ if (next.IsKind(SyntaxKind.IdentifierToken))
+ {
+ if (token.IsKind(SyntaxKind.OpenBracketToken))
+ {
+ return false;
+ }
+ // Space after the ,
+ // unmanaged[Cdecl, Thiscall
+ else if (token.IsKind(SyntaxKind.CommaToken))
+ {
+ return true;
+ }
+ }
+
+ // No space between identifier and comma
+ // unmanaged[Cdecl,
+ if (next.IsKind(SyntaxKind.CommaToken))
+ {
+ return false;
+ }
+
+ // No space before the ]
+ // unmanaged[Cdecl]
+ if (next.IsKind(SyntaxKind.CloseBracketToken))
+ {
+ return false;
+ }
+ }
+
+ // No space after the < in function pointer parameter lists
+ // delegate* in function pointer parameter lists
+ // delegate*
+ if (next.IsKind(SyntaxKind.GreaterThanToken) && next.Parent.IsKind(SyntaxKind.FunctionPointerParameterList))
+ {
+ return false;
}
if (token.IsKind(SyntaxKind.EqualsGreaterThanToken) || next.IsKind(SyntaxKind.EqualsGreaterThanToken))
@@ -501,6 +606,19 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next)
return true;
}
+ // No space before an asterisk that's part of a PointerTypeSyntax.
+ if (next.IsKind(SyntaxKind.AsteriskToken) && next.Parent is PointerTypeSyntax)
+ {
+ return false;
+ }
+
+ // The last asterisk of a pointer declaration should be followed by a space.
+ if (token.IsKind(SyntaxKind.AsteriskToken) && token.Parent is PointerTypeSyntax &&
+ (next.IsKind(SyntaxKind.IdentifierToken) || next.Parent.IsKind(SyntaxKind.IndexerDeclaration)))
+ {
+ return true;
+ }
+
if (IsKeyword(token.Kind()))
{
if (!next.IsKind(SyntaxKind.ColonToken) &&
diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
index ce8631fc49ca7..82781686a4f44 100644
--- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
+++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
@@ -688,7 +688,7 @@ public void Win32ResourceArguments()
var parsedArgs = DefaultParse(args, WorkingDirectory);
var compilation = CreateCompilation(new SyntaxTree[0]);
IEnumerable errors;
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_CantOpenWin32Manifest, errors.First().Code);
Assert.Equal(2, errors.First().Arguments.Count());
@@ -700,7 +700,7 @@ public void Win32ResourceArguments()
parsedArgs = DefaultParse(args, WorkingDirectory);
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_CantOpenIcon, errors.First().Code);
Assert.Equal(2, errors.First().Arguments.Count());
@@ -711,7 +711,7 @@ public void Win32ResourceArguments()
};
parsedArgs = DefaultParse(args, WorkingDirectory);
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_CantOpenWin32Res, errors.First().Code);
Assert.Equal(2, errors.First().Arguments.Count());
@@ -722,7 +722,7 @@ public void Win32ResourceArguments()
};
parsedArgs = DefaultParse(args, WorkingDirectory);
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_CantOpenWin32Res, errors.First().Code);
Assert.Equal(2, errors.First().Arguments.Count());
@@ -733,7 +733,7 @@ public void Win32ResourceArguments()
};
parsedArgs = DefaultParse(args, WorkingDirectory);
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_CantOpenIcon, errors.First().Code);
Assert.Equal(2, errors.First().Arguments.Count());
@@ -744,7 +744,7 @@ public void Win32ResourceArguments()
};
parsedArgs = DefaultParse(args, WorkingDirectory);
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_CantOpenWin32Manifest, errors.First().Code);
Assert.Equal(2, errors.First().Arguments.Count());
@@ -813,7 +813,7 @@ public void Win32IconContainsGarbage()
var compilation = CreateCompilation(new SyntaxTree[0]);
IEnumerable errors;
- CSharpCompiler.GetWin32ResourcesInternal(MessageProvider.Instance, parsedArgs, compilation, out errors);
+ CSharpCompiler.GetWin32ResourcesInternal(StandardFileSystem.Instance, MessageProvider.Instance, parsedArgs, compilation, out errors);
Assert.Equal(1, errors.Count());
Assert.Equal((int)ErrorCode.ERR_ErrorBuildingWin32Resources, errors.First().Code);
Assert.Equal(1, errors.First().Arguments.Count());
@@ -1808,35 +1808,6 @@ public void LangVersion_ListLangVersions()
}
}
-
- [Fact]
- public void OptimizationLevelAdded_Canary()
- {
- // When OptimizationLevel is changed, this test will break. This list must be checked:
- // - update OptimizationLevelFacts.ToPdbSerializedString
- // - update tests that call this method
- // - update docs\features\compilation-from-portable-pdb.md
- // NOTE: release is duplicated because the return value for release is the same regardless of the debugPlusMode bool
- AssertEx.SetEqual(new[] { "release", "release", "debug", "debug-plus" },
- Enum.GetValues(typeof(OptimizationLevel)).Cast().SelectMany(l => new[] { l.ToPdbSerializedString(false), l.ToPdbSerializedString(true) }));
- }
-
- [Theory,
- InlineData("release", true, OptimizationLevel.Release, false),
- InlineData("debug", true, OptimizationLevel.Debug, false),
- InlineData("debug-plus", true, OptimizationLevel.Debug, true),
- InlineData("other", false, OptimizationLevel.Debug, false),
- InlineData(null, false, OptimizationLevel.Debug, false)]
- public void OptimizationLevel_ParsePdbSerializedString(string input, bool success, OptimizationLevel expected, bool expectedDebugPlusMode)
- {
- Assert.Equal(success, OptimizationLevelFacts.TryParsePdbSerializedString(input, out var optimization, out var debugPlusMode));
- Assert.Equal(expected, optimization);
- Assert.Equal(expectedDebugPlusMode, debugPlusMode);
-
- // The canary check is a reminder that this test needs to be updated when an optimization level is added
- OptimizationLevelAdded_Canary();
- }
-
[Fact]
[WorkItem(546961, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546961")]
public void Define()
@@ -8698,7 +8669,7 @@ public void IOFailure_DisposeOutputFile()
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var exePath = Path.Combine(Path.GetDirectoryName(srcPath), "test.exe");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/preferreduilang:en", $"/out:{exePath}", srcPath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == exePath)
{
@@ -8707,7 +8678,7 @@ public void IOFailure_DisposeOutputFile()
}
return File.Open(file, mode, access, share);
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
@@ -8721,7 +8692,7 @@ public void IOFailure_DisposePdbFile()
var exePath = Path.Combine(Path.GetDirectoryName(srcPath), "test.exe");
var pdbPath = Path.ChangeExtension(exePath, "pdb");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/preferreduilang:en", "/debug", $"/out:{exePath}", srcPath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == pdbPath)
{
@@ -8730,7 +8701,7 @@ public void IOFailure_DisposePdbFile()
}
return File.Open(file, mode, access, share);
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
@@ -8743,7 +8714,7 @@ public void IOFailure_DisposeXmlFile()
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var xmlPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.xml");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/preferreduilang:en", $"/doc:{xmlPath}", srcPath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == xmlPath)
{
@@ -8752,7 +8723,7 @@ public void IOFailure_DisposeXmlFile()
}
return File.Open(file, mode, access, share);
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
@@ -8767,7 +8738,7 @@ public void IOFailure_DisposeSourceLinkFile(string format)
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var sourceLinkPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.json");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/preferreduilang:en", "/debug:" + format, $"/sourcelink:{sourceLinkPath}", srcPath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == sourceLinkPath)
{
@@ -8782,7 +8753,7 @@ public void IOFailure_DisposeSourceLinkFile(string format)
}
return File.Open(file, mode, access, share);
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
@@ -8795,7 +8766,7 @@ public void IOFailure_OpenOutputFile()
string sourcePath = MakeTrivialExe();
string exePath = Path.Combine(Path.GetDirectoryName(sourcePath), "test.exe");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/preferreduilang:en", $"/out:{exePath}", sourcePath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == exePath)
{
@@ -8803,7 +8774,7 @@ public void IOFailure_OpenOutputFile()
}
return File.Open(file, mode, access, share);
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
@@ -8821,7 +8792,7 @@ public void IOFailure_OpenPdbFileNotCalled()
string exePath = Path.Combine(Path.GetDirectoryName(sourcePath), "test.exe");
string pdbPath = Path.ChangeExtension(exePath, ".pdb");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/debug-", $"/out:{exePath}", sourcePath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == pdbPath)
{
@@ -8829,7 +8800,7 @@ public void IOFailure_OpenPdbFileNotCalled()
}
return File.Open(file, (FileMode)mode, (FileAccess)access, (FileShare)share);
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(0, csc.Run(outWriter));
@@ -8846,7 +8817,7 @@ public void IOFailure_OpenXmlFinal()
string sourcePath = MakeTrivialExe();
string xmlPath = Path.Combine(WorkingDirectory, "Test.xml");
var csc = CreateCSharpCompiler(null, WorkingDirectory, new[] { "/nologo", "/preferreduilang:en", "/doc:" + xmlPath, sourcePath });
- csc.FileOpen = (file, mode, access, share) =>
+ csc.FileSystem = TestableFileSystem.CreateForStandard(openFileFunc: (file, mode, access, share) =>
{
if (file == xmlPath)
{
@@ -8856,7 +8827,7 @@ public void IOFailure_OpenXmlFinal()
{
return File.Open(file, (FileMode)mode, (FileAccess)access, (FileShare)share);
}
- };
+ });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
int exitCode = csc.Run(outWriter);
diff --git a/src/Compilers/CSharp/Test/Emit/PDB/CSharpDeterministicBuildCompilationTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/CSharpDeterministicBuildCompilationTests.cs
index 50e4bcc9c546b..7b67e0ef72645 100644
--- a/src/Compilers/CSharp/Test/Emit/PDB/CSharpDeterministicBuildCompilationTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/PDB/CSharpDeterministicBuildCompilationTests.cs
@@ -58,13 +58,12 @@ private static void TestDeterministicCompilationCSharp(
TestMetadataReferenceInfo[] metadataReferences,
int? debugDocumentsCount = null)
{
+ var targetFramework = TargetFramework.NetCoreApp;
var originalCompilation = CreateCompilation(
syntaxTrees,
references: metadataReferences.SelectAsArray(r => r.MetadataReference),
options: compilationOptions,
- // TFM needs to specified so the references are always the same, and not
- // dependent on the testrun environment
- targetFramework: TargetFramework.NetCoreApp);
+ targetFramework: targetFramework);
var peBlob = originalCompilation.EmitToArray(options: emitOptions);
@@ -88,7 +87,7 @@ private static void TestDeterministicCompilationCSharp(
Assert.Equal(debugDocumentsCount ?? syntaxTrees.Length, pdbReader.Documents.Count);
VerifyCompilationOptions(compilationOptions, originalCompilation, emitOptions, compilationOptionsReader, langVersion, syntaxTrees.Length);
- DeterministicBuildCompilationTestHelpers.VerifyReferenceInfo(metadataReferences, metadataReferenceReader);
+ DeterministicBuildCompilationTestHelpers.VerifyReferenceInfo(metadataReferences, targetFramework, metadataReferenceReader);
}
}
}
@@ -342,8 +341,6 @@ private static IEnumerable GetCompilationOptions()
yield return defaultOptions.WithNullableContextOptions(NullableContextOptions.Disable);
yield return defaultOptions.WithNullableContextOptions(NullableContextOptions.Warnings);
yield return defaultOptions.WithOptimizationLevel(OptimizationLevel.Release);
- yield return defaultOptions.WithDebugPlusMode(true);
- yield return defaultOptions.WithOptimizationLevel(OptimizationLevel.Release).WithDebugPlusMode(true);
yield return defaultOptions.WithAssemblyIdentityComparer(new DesktopAssemblyIdentityComparer(new AssemblyPortabilityPolicy(suppressSilverlightLibraryAssembliesPortability: false, suppressSilverlightPlatformAssembliesPortability: false)));
yield return defaultOptions.WithAssemblyIdentityComparer(new DesktopAssemblyIdentityComparer(new AssemblyPortabilityPolicy(suppressSilverlightLibraryAssembliesPortability: true, suppressSilverlightPlatformAssembliesPortability: false)));
yield return defaultOptions.WithAssemblyIdentityComparer(new DesktopAssemblyIdentityComparer(new AssemblyPortabilityPolicy(suppressSilverlightLibraryAssembliesPortability: false, suppressSilverlightPlatformAssembliesPortability: true)));
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
index 4e1e3a02e33f6..b8b457c0e01bb 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
@@ -18,7 +18,7 @@
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
- public class LambdaTests : CompilingTestBase
+ public class LambdaTests : CSharpTestBase
{
[Fact, WorkItem(37456, "https://github.com/dotnet/roslyn/issues/37456")]
public void Verify37456()
@@ -568,10 +568,10 @@ static void Main()
";
var vbMetadata = vbProject.EmitToArray(options: new EmitOptions(metadataOnly: true));
var csProject = CreateCompilation(Parse(csSource), new[] { MetadataReference.CreateFromImage(vbMetadata) });
-
- var diagnostics = csProject.GetDiagnostics().Select(DumpDiagnostic);
- Assert.Equal(1, diagnostics.Count());
- Assert.Equal("'x' error CS0721: 'GC': static types cannot be used as parameters", diagnostics.First());
+ csProject.VerifyDiagnostics(
+ // (6,15): error CS0721: 'GC': static types cannot be used as parameters
+ // M.F = x=>{};
+ Diagnostic(ErrorCode.ERR_ParameterIsStaticClass, "x").WithArguments("System.GC").WithLocation(6, 15));
}
[WorkItem(540251, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540251")]
diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs
index 12eccfd572f1d..f6189db3f1727 100644
--- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs
+++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs
@@ -20772,5 +20772,18 @@ partial void M(in int i) {}
// partial void M(in int i) {}
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "M").WithArguments("C.M(in int)").WithLocation(4, 18));
}
+
+ [Fact]
+ public void MethodWithNoReturnTypeShouldNotComplainAboutStaticCtor()
+ {
+ CreateCompilation(@"
+class X
+{
+ private static Y(int i) {}
+}").VerifyDiagnostics(
+ // (4,20): error CS1520: Method must have a return type
+ // private static Y(int i) {}
+ Diagnostic(ErrorCode.ERR_MemberNeedsType, "Y").WithLocation(4, 20));
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs
index 4101ceb06b9cc..2e8fa09adecb3 100644
--- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs
@@ -198,6 +198,50 @@ public void TestNormalizeStatement1()
TestNormalizeStatement("Func f = blah;", "Func f = blah;");
}
+ [Theory]
+ [InlineData("int*p;", "int* p;")]
+ [InlineData("int *p;", "int* p;")]
+ [InlineData("int*p1,p2;", "int* p1, p2;")]
+ [InlineData("int *p1, p2;", "int* p1, p2;")]
+ [InlineData("int**p;", "int** p;")]
+ [InlineData("int **p;", "int** p;")]
+ [InlineData("int**p1,p2;", "int** p1, p2;")]
+ [InlineData("int **p1, p2;", "int** p1, p2;")]
+ [WorkItem(49733, "https://github.com/dotnet/roslyn/issues/49733")]
+ public void TestNormalizeAsteriskInPointerDeclaration(string text, string expected)
+ {
+ TestNormalizeStatement(text, expected);
+ }
+
+ [Fact]
+ [WorkItem(49733, "https://github.com/dotnet/roslyn/issues/49733")]
+ public void TestNormalizeAsteriskInPointerReturnTypeOfIndexer()
+ {
+ var text = @"public unsafe class C
+{
+ int*this[int x,int y]{get=>(int*)0;}
+}";
+ var expected = @"public unsafe class C
+{
+ int* this[int x, int y] { get => (int*)0; }
+}";
+ TestNormalizeDeclaration(text, expected);
+ }
+
+ [Fact]
+ public void TestNormalizeAsteriskInVoidPointerCast()
+ {
+ var text = @"public unsafe class C
+{
+ void*this[int x,int y]{get => ( void * ) 0;}
+}";
+ var expected = @"public unsafe class C
+{
+ void* this[int x, int y] { get => (void*)0; }
+}";
+ TestNormalizeDeclaration(text, expected);
+ }
+
private void TestNormalizeStatement(string text, string expected)
{
var node = SyntaxFactory.ParseStatement(text);
@@ -649,6 +693,92 @@ public void TestNormalizeTuples()
TestNormalizeDeclaration("public (string prefix,string uri)Foo()", "public (string prefix, string uri) Foo()");
}
+ [Fact]
+ [WorkItem(50664, "https://github.com/dotnet/roslyn/issues/50664")]
+ public void TestNormalizeFunctionPointer()
+ {
+ var content =
+@"unsafe class C
+{
+ delegate * < int , int > functionPointer;
+}";
+
+ var expected =
+@"unsafe class C
+{
+ delegate* functionPointer;
+}";
+
+ TestNormalizeDeclaration(content, expected);
+ }
+
+ [Fact]
+ [WorkItem(50664, "https://github.com/dotnet/roslyn/issues/50664")]
+ public void TestNormalizeFunctionPointerWithManagedCallingConvention()
+ {
+ var content =
+@"unsafe class C
+{
+ delegate *managed < int , int > functionPointer;
+}";
+
+ var expected =
+@"unsafe class C
+{
+ delegate* managed functionPointer;
+}";
+
+ TestNormalizeDeclaration(content, expected);
+ }
+
+ [Fact]
+ [WorkItem(50664, "https://github.com/dotnet/roslyn/issues/50664")]
+ public void TestNormalizeFunctionPointerWithUnmanagedCallingConvention()
+ {
+ var content =
+@"unsafe class C
+{
+ delegate *unmanaged < int , int > functionPointer;
+}";
+
+ var expected =
+@"unsafe class C
+{
+ delegate* unmanaged functionPointer;
+}";
+
+ TestNormalizeDeclaration(content, expected);
+ }
+
+ [Fact]
+ [WorkItem(50664, "https://github.com/dotnet/roslyn/issues/50664")]
+ public void TestNormalizeFunctionPointerWithUnmanagedCallingConventionAndSpecifiers()
+ {
+ var content =
+@"unsafe class C
+{
+ delegate *unmanaged [ Cdecl , Thiscall ] < int , int > functionPointer;
+}";
+
+ var expected =
+@"unsafe class C
+{
+ delegate* unmanaged[Cdecl, Thiscall] functionPointer;
+}";
+
+ TestNormalizeDeclaration(content, expected);
+ }
+
+ [Fact]
+ [WorkItem(49732, "https://github.com/dotnet/roslyn/issues/49732")]
+ public void TestNormalizeXmlInDocComment()
+ {
+ var code = @"///
+/// If this method succeeds, it returns S_OK.
+/// ";
+ TestNormalizeDeclaration(code, code);
+ }
+
[Theory]
[InlineData("_=()=>{};", "_ = () =>\r\n{\r\n};")]
[InlineData("_=x=>{};", "_ = x =>\r\n{\r\n};")]
diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs
index 40a732904aa5a..79769dc19af09 100644
--- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs
+++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs
@@ -391,6 +391,82 @@ public void Initialize(GeneratorInitializationContext context) {{ }}
}
}
+ [Fact]
+ [WorkItem(52035, "https://github.com/dotnet/roslyn/issues/52035")]
+ public void TestLoadedAnalyzerOrderIsDeterministic()
+ {
+ AnalyzerFileReference reference = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location);
+
+ var csharpAnalyzers = reference.GetAnalyzers(LanguageNames.CSharp).Select(a => a.GetType().FullName).ToArray();
+ Assert.Equal(4, csharpAnalyzers.Length);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedAnalyzer", csharpAnalyzers[0]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzer", csharpAnalyzers[1]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzerCS", csharpAnalyzers[2]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSVB", csharpAnalyzers[3]);
+
+ var vbAnalyzers = reference.GetAnalyzers(LanguageNames.VisualBasic).Select(a => a.GetType().FullName).ToArray();
+ Assert.Equal(4, vbAnalyzers.Length);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedAnalyzer", vbAnalyzers[0]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzer", vbAnalyzers[1]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzerVB", vbAnalyzers[2]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSVB", vbAnalyzers[3]);
+
+ // analyzers return C#, then VB, including duplicates
+ var allAnalyzers = reference.GetAnalyzersForAllLanguages().Select(a => a.GetType().FullName).ToArray();
+ Assert.Equal(8, allAnalyzers.Length);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedAnalyzer", allAnalyzers[0]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzer", allAnalyzers[1]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzerCS", allAnalyzers[2]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSVB", allAnalyzers[3]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedAnalyzer", allAnalyzers[4]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzer", allAnalyzers[5]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzerVB", allAnalyzers[6]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSVB", allAnalyzers[7]);
+ }
+
+ [ConditionalFact(typeof(CoreClrOnly), Reason = "Can't load a framework targeting generator, which these are in desktop")]
+ [WorkItem(52035, "https://github.com/dotnet/roslyn/issues/52035")]
+ public void TestLoadedGeneratorOrderIsDeterministic()
+ {
+ AnalyzerFileReference reference = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location);
+
+ var csharpGenerators = reference.GetGenerators(LanguageNames.CSharp).Select(a => a.GetType().FullName).ToArray();
+ Assert.Equal(8, csharpGenerators.Length);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedGenerator", csharpGenerators[0]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestGenerator", csharpGenerators[1]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.BaseGenerator", csharpGenerators[2]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.CSharpAndVisualBasicGenerator", csharpGenerators[3]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.ExplicitCSharpOnlyGenerator", csharpGenerators[4]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.SubClassedGenerator", csharpGenerators[5]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestGenerator", csharpGenerators[6]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicAndCSharpGenerator", csharpGenerators[7]);
+
+ var vbGenerators = reference.GetGenerators(LanguageNames.VisualBasic).Select(a => a.GetType().FullName).ToArray();
+ Assert.Equal(3, vbGenerators.Length);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.CSharpAndVisualBasicGenerator", vbGenerators[0]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicAndCSharpGenerator", vbGenerators[1]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicOnlyGenerator", vbGenerators[2]);
+
+ // generators load in language order (C#, F#, VB), and *do not* include duplicates
+ var allGenerators = reference.GetGeneratorsForAllLanguages().Select(g => g.GetType().FullName).ToArray();
+ Assert.Equal(10, allGenerators.Length);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedGenerator", allGenerators[0]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestGenerator", allGenerators[1]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.BaseGenerator", allGenerators[2]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.CSharpAndVisualBasicGenerator", allGenerators[3]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.ExplicitCSharpOnlyGenerator", allGenerators[4]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.SubClassedGenerator", allGenerators[5]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestGenerator", allGenerators[6]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicAndCSharpGenerator", allGenerators[7]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.FSharpGenerator", allGenerators[8]);
+ Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicOnlyGenerator", allGenerators[9]);
+ }
+
+ // NOTE: the order in which these are emitted can change the test 'TestLoadedAnalyzerOrderIsDeterministic'
+ // and other determinism tests in this file.
+ // Ensure you do not re-arrange them alphabetically, as that will invalidate the tests, without
+ // explicitly failing them
+
[DiagnosticAnalyzer(LanguageNames.CSharp, new string[] { LanguageNames.VisualBasic })]
public class TestAnalyzer : DiagnosticAnalyzer
{
diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs
index 025896ce62492..8c16096b12924 100644
--- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs
+++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs
@@ -10,6 +10,7 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
@@ -226,13 +227,13 @@ public static ImmutableArray SelectAsArray(this Immutab
///
/// Maps an immutable array through a function that returns ValueTasks, returning the new ImmutableArray.
///
- public static async ValueTask> SelectAsArrayAsync(this ImmutableArray array, Func> selector)
+ public static async ValueTask> SelectAsArrayAsync(this ImmutableArray array, Func> selector, CancellationToken cancellationToken)
{
var builder = ArrayBuilder.GetInstance(array.Length);
foreach (var item in array)
{
- builder.Add(await selector(item).ConfigureAwait(false));
+ builder.Add(await selector(item, cancellationToken).ConfigureAwait(false));
}
return builder.ToImmutableAndFree();
diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs
index 6ff955bfe8003..b855b0854210c 100644
--- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs
+++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs
@@ -110,7 +110,7 @@ public void Close(DiagnosticBag diagnostics)
private Stream OpenFileStream()
{
- return _streamToDispose = _compiler.FileOpen(_filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
+ return _streamToDispose = _compiler.FileSystem.OpenFile(_filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
}
private void ReportOpenFileDiagnostic(DiagnosticBag diagnostics, Exception e)
diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerRelativePathResolver.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerRelativePathResolver.cs
new file mode 100644
index 0000000000000..c8a093686851f
--- /dev/null
+++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.CompilerRelativePathResolver.cs
@@ -0,0 +1,35 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Linq;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis
+{
+ internal abstract partial class CommonCompiler
+ {
+ ///
+ /// Looks for metadata references among the assembly file references given to the compilation when constructed.
+ /// When scripts are included into a project we don't want #r's to reference other assemblies than those
+ /// specified explicitly in the project references.
+ ///
+ internal sealed class CompilerRelativePathResolver : RelativePathResolver
+ {
+ internal ICommonCompilerFileSystem FileSystem { get; }
+
+ internal CompilerRelativePathResolver(ICommonCompilerFileSystem fileSystem, ImmutableArray searchPaths, string? baseDirectory)
+ : base(searchPaths, baseDirectory)
+ {
+ FileSystem = fileSystem;
+ }
+
+ protected override bool FileExists(string fullPath) => FileSystem.FileExists(fullPath);
+ }
+ }
+}
diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
index b71fde50eb41b..960a07d8e35af 100644
--- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
+++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
@@ -80,6 +80,11 @@ internal abstract partial class CommonCompiler
///
public IReadOnlySet EmbeddedSourcePaths { get; }
+ ///
+ /// The used to access the file system inside this instance.
+ ///
+ internal ICommonCompilerFileSystem FileSystem { get; set; } = StandardFileSystem.Instance;
+
private readonly HashSet _reportedDiagnostics = new HashSet();
public abstract Compilation? CreateCompilation(
@@ -200,12 +205,16 @@ internal string GetCultureName()
internal virtual Func GetMetadataProvider()
{
- return (path, properties) => MetadataReference.CreateFromFile(path, properties);
+ return (path, properties) =>
+ {
+ var peStream = FileSystem.OpenFileWithNormalizedException(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return MetadataReference.CreateFromFile(peStream, path, properties);
+ };
}
internal virtual MetadataReferenceResolver GetCommandLineMetadataReferenceResolver(TouchedFileLogger? loggerOpt)
{
- var pathResolver = new RelativePathResolver(Arguments.ReferencePaths, Arguments.BaseDirectory);
+ var pathResolver = new CompilerRelativePathResolver(FileSystem, Arguments.ReferencePaths, Arguments.BaseDirectory!);
return new LoggingMetadataFileReferenceResolver(pathResolver, GetMetadataProvider(), loggerOpt);
}
@@ -266,8 +275,7 @@ internal List ResolveMetadataReferences(
}
else
{
- using var data = OpenFileForReadWithSmallBufferOptimization(filePath);
- normalizedFilePath = data.Name;
+ using var data = OpenFileForReadWithSmallBufferOptimization(filePath, out normalizedFilePath);
return EncodedStringText.Create(data, _fallbackEncoding, Arguments.Encoding, Arguments.ChecksumAlgorithm, canBeEmbedded: EmbeddedSourcePaths.Contains(file.Path));
}
}
@@ -358,8 +366,7 @@ internal bool TryGetAnalyzerConfigSet(
{
try
{
- var data = OpenFileForReadWithSmallBufferOptimization(filePath);
- normalizedPath = data.Name;
+ var data = OpenFileForReadWithSmallBufferOptimization(filePath, out normalizedPath);
using (var reader = new StreamReader(data, Encoding.UTF8))
{
return reader.ReadToEnd();
@@ -373,25 +380,24 @@ internal bool TryGetAnalyzerConfigSet(
}
}
- private static FileStream OpenFileForReadWithSmallBufferOptimization(string filePath)
- {
+ private Stream OpenFileForReadWithSmallBufferOptimization(string filePath, out string normalizedFilePath)
// PERF: Using a very small buffer size for the FileStream opens up an optimization within EncodedStringText/EmbeddedText where
// we read the entire FileStream into a byte array in one shot. For files that are actually smaller than the buffer
// size, FileStream.Read still allocates the internal buffer.
- return new FileStream(
+ => FileSystem.OpenFileEx(
filePath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 1,
- options: FileOptions.None);
- }
+ options: FileOptions.None,
+ out normalizedFilePath);
internal EmbeddedText? TryReadEmbeddedFileContent(string filePath, DiagnosticBag diagnostics)
{
try
{
- using (var stream = OpenFileForReadWithSmallBufferOptimization(filePath))
+ using (var stream = OpenFileForReadWithSmallBufferOptimization(filePath, out _))
{
const int LargeObjectHeapLimit = 80 * 1024;
if (stream.Length < LargeObjectHeapLimit)
@@ -1187,7 +1193,7 @@ private void CompileAndEmit(
using (xmlStreamDisposerOpt)
{
- using (var win32ResourceStreamOpt = GetWin32Resources(MessageProvider, Arguments, compilation, diagnostics))
+ using (var win32ResourceStreamOpt = GetWin32Resources(FileSystem, MessageProvider, Arguments, compilation, diagnostics))
{
if (HasUnsuppressableErrors(diagnostics))
{
@@ -1464,17 +1470,6 @@ private static void ReportAnalyzerExecutionTime(TextWriter consoleOutput, Analyz
///
protected abstract string GetOutputFileName(Compilation compilation, CancellationToken cancellationToken);
- ///
- /// Test hook for intercepting File.Open.
- ///
- internal Func FileOpen
- {
- get { return _fileOpen ?? ((path, mode, access, share) => new FileStream(path, mode, access, share)); }
- set { _fileOpen = value; }
- }
-
- private Func? _fileOpen;
-
private Stream? OpenFile(
string filePath,
DiagnosticBag diagnostics,
@@ -1484,7 +1479,7 @@ internal Func FileOpen
{
try
{
- return FileOpen(filePath, mode, access, share);
+ return FileSystem.OpenFile(filePath, mode, access, share);
}
catch (Exception e)
{
@@ -1495,18 +1490,20 @@ internal Func FileOpen
// internal for testing
internal static Stream? GetWin32ResourcesInternal(
+ ICommonCompilerFileSystem fileSystem,
CommonMessageProvider messageProvider,
CommandLineArguments arguments,
Compilation compilation,
out IEnumerable errors)
{
var diagnostics = DiagnosticBag.GetInstance();
- var stream = GetWin32Resources(messageProvider, arguments, compilation, diagnostics);
+ var stream = GetWin32Resources(fileSystem, messageProvider, arguments, compilation, diagnostics);
errors = diagnostics.ToReadOnlyAndFree().SelectAsArray(diag => new DiagnosticInfo(messageProvider, diag.IsWarningAsError, diag.Code, (object[])diag.Arguments));
return stream;
}
private static Stream? GetWin32Resources(
+ ICommonCompilerFileSystem fileSystem,
CommonMessageProvider messageProvider,
CommandLineArguments arguments,
Compilation compilation,
@@ -1514,12 +1511,12 @@ internal Func FileOpen
{
if (arguments.Win32ResourceFile != null)
{
- return OpenStream(messageProvider, arguments.Win32ResourceFile, arguments.BaseDirectory, messageProvider.ERR_CantOpenWin32Resource, diagnostics);
+ return OpenStream(fileSystem, messageProvider, arguments.Win32ResourceFile, arguments.BaseDirectory, messageProvider.ERR_CantOpenWin32Resource, diagnostics);
}
- using (Stream? manifestStream = OpenManifestStream(messageProvider, compilation.Options.OutputKind, arguments, diagnostics))
+ using (Stream? manifestStream = OpenManifestStream(fileSystem, messageProvider, compilation.Options.OutputKind, arguments, diagnostics))
{
- using (Stream? iconStream = OpenStream(messageProvider, arguments.Win32Icon, arguments.BaseDirectory, messageProvider.ERR_CantOpenWin32Icon, diagnostics))
+ using (Stream? iconStream = OpenStream(fileSystem, messageProvider, arguments.Win32Icon, arguments.BaseDirectory, messageProvider.ERR_CantOpenWin32Icon, diagnostics))
{
try
{
@@ -1535,14 +1532,14 @@ internal Func FileOpen
return null;
}
- private static Stream? OpenManifestStream(CommonMessageProvider messageProvider, OutputKind outputKind, CommandLineArguments arguments, DiagnosticBag diagnostics)
+ private static Stream? OpenManifestStream(ICommonCompilerFileSystem fileSystem, CommonMessageProvider messageProvider, OutputKind outputKind, CommandLineArguments arguments, DiagnosticBag diagnostics)
{
return outputKind.IsNetModule()
? null
- : OpenStream(messageProvider, arguments.Win32Manifest, arguments.BaseDirectory, messageProvider.ERR_CantOpenWin32Manifest, diagnostics);
+ : OpenStream(fileSystem, messageProvider, arguments.Win32Manifest, arguments.BaseDirectory, messageProvider.ERR_CantOpenWin32Manifest, diagnostics);
}
- private static Stream? OpenStream(CommonMessageProvider messageProvider, string? path, string? baseDirectory, int errorCode, DiagnosticBag diagnostics)
+ private static Stream? OpenStream(ICommonCompilerFileSystem fileSystem, CommonMessageProvider messageProvider, string? path, string? baseDirectory, int errorCode, DiagnosticBag diagnostics)
{
if (path == null)
{
@@ -1557,7 +1554,7 @@ internal Func FileOpen
try
{
- return new FileStream(fullPath, FileMode.Open, FileAccess.Read);
+ return fileSystem.OpenFile(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception ex)
{
diff --git a/src/Compilers/Core/Portable/Compilation/ControlFlowAnalysis.cs b/src/Compilers/Core/Portable/Compilation/ControlFlowAnalysis.cs
index 00d2657557572..af90e0885af16 100644
--- a/src/Compilers/Core/Portable/Compilation/ControlFlowAnalysis.cs
+++ b/src/Compilers/Core/Portable/Compilation/ControlFlowAnalysis.cs
@@ -38,7 +38,7 @@ public abstract class ControlFlowAnalysis
public abstract ImmutableArray ReturnStatements { get; }
///
- /// Returns true iff analysis was successful. Analysis can fail if the region does not properly span a single expression,
+ /// Returns true if and only if analysis was successful. Analysis can fail if the region does not properly span a single expression,
/// a single statement, or a contiguous series of statements within the enclosing block.
///
public abstract bool Succeeded { get; }
diff --git a/src/Compilers/Core/Portable/Compilation/DataFlowAnalysis.cs b/src/Compilers/Core/Portable/Compilation/DataFlowAnalysis.cs
index b9ec8795d52c6..3e834d0aa6ac0 100644
--- a/src/Compilers/Core/Portable/Compilation/DataFlowAnalysis.cs
+++ b/src/Compilers/Core/Portable/Compilation/DataFlowAnalysis.cs
@@ -104,7 +104,7 @@ public abstract class DataFlowAnalysis
public abstract ImmutableArray UsedLocalFunctions { get; }
///
- /// Returns true iff analysis was successful. Analysis can fail if the region does not
+ /// Returns true if and only if analysis was successful. Analysis can fail if the region does not
/// properly span a single expression, a single statement, or a contiguous series of
/// statements within the enclosing block.
///
diff --git a/src/Compilers/Core/Portable/Compilation/OptimizationLevel.cs b/src/Compilers/Core/Portable/Compilation/OptimizationLevel.cs
index 7e1948a13b88c..ac457e8fa9d22 100644
--- a/src/Compilers/Core/Portable/Compilation/OptimizationLevel.cs
+++ b/src/Compilers/Core/Portable/Compilation/OptimizationLevel.cs
@@ -48,42 +48,46 @@ public enum OptimizationLevel
internal static class OptimizationLevelFacts
{
+ internal static (OptimizationLevel OptimizationLevel, bool DebugPlus) DefaultValues => (OptimizationLevel.Debug, false);
+
public static string ToPdbSerializedString(this OptimizationLevel optimization, bool debugPlusMode)
- => optimization switch
- {
- OptimizationLevel.Release => "release",
- OptimizationLevel.Debug => debugPlusMode ? "debug-plus" : "debug",
- _ => throw ExceptionUtilities.UnexpectedValue(optimization)
- };
+ => (optimization, debugPlusMode) switch
+ {
+ (OptimizationLevel.Release, true) => "release-debug-plus",
+ (OptimizationLevel.Release, false) => "release",
+ (OptimizationLevel.Debug, true) => "debug-plus",
+ (OptimizationLevel.Debug, false) => "debug",
+ _ => throw ExceptionUtilities.UnexpectedValue(optimization)
+ };
public static bool TryParsePdbSerializedString(string value, out OptimizationLevel optimizationLevel, out bool debugPlusMode)
{
- if (value == "release")
- {
- optimizationLevel = OptimizationLevel.Release;
- debugPlusMode = false;
- return true;
- }
- else if (value == "debug")
+ switch (value)
{
- optimizationLevel = OptimizationLevel.Debug;
- debugPlusMode = false;
- return true;
+ case "release-debug-plus":
+ optimizationLevel = OptimizationLevel.Release;
+ debugPlusMode = true;
+ return true;
+ case "release":
+ optimizationLevel = OptimizationLevel.Release;
+ debugPlusMode = false;
+ return true;
+ case "debug-plus":
+ optimizationLevel = OptimizationLevel.Debug;
+ debugPlusMode = true;
+ return true;
+ case "debug":
+ optimizationLevel = OptimizationLevel.Debug;
+ debugPlusMode = false;
+ return true;
+ default:
+ optimizationLevel = OptimizationLevel.Debug;
+ debugPlusMode = false;
+ return false;
}
- else if (value == "debug-plus")
- {
- optimizationLevel = OptimizationLevel.Debug;
- debugPlusMode = true;
- return true;
- }
-
- optimizationLevel = default;
- debugPlusMode = default;
- return false;
}
}
-
internal static partial class EnumBounds
{
internal static bool IsValid(this OptimizationLevel value)
diff --git a/src/Compilers/Core/Portable/Compilation/SemanticModel.cs b/src/Compilers/Core/Portable/Compilation/SemanticModel.cs
index d7c7447a81c1b..d147e4dda26ff 100644
--- a/src/Compilers/Core/Portable/Compilation/SemanticModel.cs
+++ b/src/Compilers/Core/Portable/Compilation/SemanticModel.cs
@@ -78,7 +78,7 @@ public SyntaxTree SyntaxTree
{
return GetOperationCore(node, cancellationToken);
}
- catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
{
// Log a Non-fatal-watson and then ignore the crash in the attempt of getting operation
Debug.Assert(false, "\n" + e.ToString());
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs
index ffc3c5095af0b..d5fac8ff065d1 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs
@@ -122,7 +122,7 @@ public async Task OnCompilationEventsGeneratedAsync(
OnCompilationEventsGenerated_NoLock(getCompilationEvents(eventQueue, additionalFiles));
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs
index c6a37673b4bf0..cb29f833221ae 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs
@@ -1408,7 +1408,7 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An
await ProcessEventAsync(completedEvent, analysisScope, analysisState, cancellationToken).ConfigureAwait(false);
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -1467,7 +1467,7 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An
return completedEvent;
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -2687,7 +2687,7 @@ ImmutableArray getOperationsToAnalyzeWithStackGuard(ImmutableArray> GetAnalyzerTypeNameMap()
+ internal ImmutableSortedDictionary> GetAnalyzerTypeNameMap()
{
return _diagnosticAnalyzers.GetExtensionTypeNameMap();
}
@@ -222,7 +222,7 @@ internal ImmutableDictionary> GetAnalyzerTypeNa
/// The PE image format is invalid.
/// IO error reading the metadata.
[PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30449")]
- private static ImmutableDictionary> GetAnalyzerTypeNameMap(string fullPath, Type attributeType, AttributeLanguagesFunc languagesFunc)
+ private static ImmutableSortedDictionary> GetAnalyzerTypeNameMap(string fullPath, Type attributeType, AttributeLanguagesFunc languagesFunc)
{
using var assembly = AssemblyMetadata.CreateFromFile(fullPath);
@@ -238,7 +238,7 @@ where supportedLanguages.Any()
from supportedLanguage in supportedLanguages
group typeName by supportedLanguage;
- return typeNameMap.ToImmutableDictionary(g => g.Key, g => g.ToImmutableHashSet());
+ return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);
}
private static IEnumerable GetSupportedLanguages(TypeDefinition typeDef, PEModule peModule, Type attributeType, AttributeLanguagesFunc languagesFunc)
@@ -347,7 +347,7 @@ private sealed class Extensions
private readonly bool _allowNetFramework;
private ImmutableArray _lazyAllExtensions;
private ImmutableDictionary> _lazyExtensionsPerLanguage;
- private ImmutableDictionary>? _lazyExtensionTypeNameMap;
+ private ImmutableSortedDictionary>? _lazyExtensionTypeNameMap;
internal Extensions(AnalyzerFileReference reference, Type attributeType, AttributeLanguagesFunc languagesFunc, bool allowNetFramework)
{
@@ -372,7 +372,7 @@ internal ImmutableArray GetExtensionsForAllLanguages(bool includeDup
private static ImmutableArray CreateExtensionsForAllLanguages(Extensions extensions, bool includeDuplicates)
{
// Get all analyzers in the assembly.
- var map = ImmutableDictionary.CreateBuilder>();
+ var map = ImmutableSortedDictionary.CreateBuilder>(StringComparer.OrdinalIgnoreCase);
extensions.AddExtensions(map);
var builder = ImmutableArray.CreateBuilder();
@@ -421,7 +421,7 @@ private static ImmutableArray CreateLanguageSpecificExtensions(strin
return builder.ToImmutable();
}
- internal ImmutableDictionary> GetExtensionTypeNameMap()
+ internal ImmutableSortedDictionary> GetExtensionTypeNameMap()
{
if (_lazyExtensionTypeNameMap == null)
{
@@ -432,9 +432,9 @@ internal ImmutableDictionary> GetExtensionTypeN
return _lazyExtensionTypeNameMap;
}
- internal void AddExtensions(ImmutableDictionary>.Builder builder)
+ internal void AddExtensions(ImmutableSortedDictionary>.Builder builder)
{
- ImmutableDictionary> analyzerTypeNameMap;
+ ImmutableSortedDictionary> analyzerTypeNameMap;
Assembly analyzerAssembly;
try
@@ -478,7 +478,7 @@ internal void AddExtensions(ImmutableDictionary.Builder builder, string language)
{
- ImmutableDictionary> analyzerTypeNameMap;
+ ImmutableSortedDictionary> analyzerTypeNameMap;
Assembly analyzerAssembly;
try
@@ -519,9 +519,9 @@ internal void AddExtensions(ImmutableArray.Builder builder, string l
}
}
- private ImmutableArray GetLanguageSpecificAnalyzers(Assembly analyzerAssembly, ImmutableDictionary> analyzerTypeNameMap, string language, ref bool reportedError)
+ private ImmutableArray GetLanguageSpecificAnalyzers(Assembly analyzerAssembly, ImmutableSortedDictionary> analyzerTypeNameMap, string language, ref bool reportedError)
{
- ImmutableHashSet? languageSpecificAnalyzerTypeNames;
+ ImmutableSortedSet? languageSpecificAnalyzerTypeNames;
if (!analyzerTypeNameMap.TryGetValue(language, out languageSpecificAnalyzerTypeNames))
{
return ImmutableArray.Empty;
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs
index 3d5ce4257d1fa..ddfb456e8690a 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs
@@ -593,7 +593,7 @@ private async Task ComputeAnalyzerSyntaxDiagnosticsAsync(AnalysisScope analysisS
await ComputeAnalyzerDiagnosticsAsync(pendingAnalysisScope, getPendingEventsOpt: null, taskToken, cancellationToken).ConfigureAwait(false);
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -868,7 +868,7 @@ await Task.WhenAll(partialTrees.Select(tree =>
FreeDriver(driver);
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -961,7 +961,7 @@ private async Task GetAnalyzerDriverAsync(CancellationToken canc
}
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -1008,7 +1008,7 @@ private async Task ComputeAnalyzerDiagnosticsCoreAsync(AnalyzerDriver driver, As
}
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -1138,7 +1138,7 @@ private async Task SetActiveTreeAnalysisTaskAsync(Func GetAnalyzerTelemetryInfoAsync(Diagnosti
var executionTime = GetAnalyzerExecutionTime(analyzer);
return new AnalyzerTelemetryInfo(actionCounts, suppressionActionCounts, executionTime);
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
diff --git a/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs b/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs
index e6b4c219f31df..3bf027cb0843d 100644
--- a/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs
+++ b/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs
@@ -398,29 +398,5 @@ internal static long GetFileLength(string fullPath)
throw new IOException(e.Message, e);
}
}
-
- internal static Stream OpenFileStream(string path)
- {
- try
- {
- return File.OpenRead(path);
- }
- catch (ArgumentException)
- {
- throw;
- }
- catch (DirectoryNotFoundException e)
- {
- throw new FileNotFoundException(e.Message, path, e);
- }
- catch (IOException)
- {
- throw;
- }
- catch (Exception e)
- {
- throw new IOException(e.Message, e);
- }
- }
}
}
diff --git a/src/Compilers/Core/Portable/FileSystem/ICommonCompilerFileSystem.cs b/src/Compilers/Core/Portable/FileSystem/ICommonCompilerFileSystem.cs
new file mode 100644
index 0000000000000..cfa7021d448cb
--- /dev/null
+++ b/src/Compilers/Core/Portable/FileSystem/ICommonCompilerFileSystem.cs
@@ -0,0 +1,73 @@
+// 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;
+using System.IO;
+
+namespace Roslyn.Utilities
+{
+ ///
+ /// Abstraction over the file system that is useful for test hooks
+ ///
+ internal interface ICommonCompilerFileSystem
+ {
+ bool FileExists(string filePath);
+
+ Stream OpenFile(string filePath, FileMode mode, FileAccess access, FileShare share);
+
+ Stream OpenFileEx(string filePath, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, out string normalizedFilePath);
+ }
+
+ internal static class CommonCompilerFileSystemExtensions
+ {
+ ///
+ /// Open a file and ensure common exception types are wrapped to .
+ ///
+ internal static Stream OpenFileWithNormalizedException(this ICommonCompilerFileSystem fileSystem, string filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
+ {
+ try
+ {
+ return fileSystem.OpenFile(filePath, fileMode, fileAccess, fileShare);
+ }
+ catch (ArgumentException)
+ {
+ throw;
+ }
+ catch (DirectoryNotFoundException e)
+ {
+ throw new FileNotFoundException(e.Message, filePath, e);
+ }
+ catch (IOException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.Message, e);
+ }
+ }
+ }
+
+ internal sealed class StandardFileSystem : ICommonCompilerFileSystem
+ {
+ public static StandardFileSystem Instance { get; } = new StandardFileSystem();
+
+ private StandardFileSystem()
+ {
+ }
+
+ public bool FileExists(string filePath) => File.Exists(filePath);
+
+ public Stream OpenFile(string filePath, FileMode mode, FileAccess access, FileShare share)
+ => new FileStream(filePath, mode, access, share);
+
+ public Stream OpenFileEx(string filePath, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, out string normalizedFilePath)
+ {
+ var fileStream = new FileStream(filePath, mode, access, share, bufferSize, options);
+ normalizedFilePath = fileStream.Name;
+ return fileStream;
+ }
+ }
+}
diff --git a/src/Compilers/Core/Portable/InternalUtilities/ConcurrentLruCache.cs b/src/Compilers/Core/Portable/InternalUtilities/ConcurrentLruCache.cs
index 579c982671bf0..15fe814d1144b 100644
--- a/src/Compilers/Core/Portable/InternalUtilities/ConcurrentLruCache.cs
+++ b/src/Compilers/Core/Portable/InternalUtilities/ConcurrentLruCache.cs
@@ -87,7 +87,7 @@ public void Add(K key, V value)
private void MoveNodeToTop(LinkedListNode node)
{
- if (!object.ReferenceEquals(_nodeList.First, node))
+ if (!ReferenceEquals(_nodeList.First, node))
{
_nodeList.Remove(node);
_nodeList.AddFirst(node);
diff --git a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs
index 8f440bad5d980..bda79fdeed995 100644
--- a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs
+++ b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs
@@ -7,6 +7,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
+#if NET20
+// Some APIs referenced by documentation comments are not available on .NET Framework 2.0.
+#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
+#endif
+
#if COMPILERCORE
namespace Microsoft.CodeAnalysis
#else
@@ -79,11 +84,10 @@ private static bool IsCurrentOperationBeingCancelled(Exception exception, Cancel
=> exception is OperationCanceledException && cancellationToken.IsCancellationRequested;
///
- /// Use in an exception filter to report a fatal error.
- /// Unless the exception is
- /// it calls . The exception is passed through (the method returns false).
+ /// Use in an exception filter to report a fatal error (by calling ), unless the
+ /// operation has been cancelled. The exception is never caught.
///
- /// False to avoid catching the exception.
+ /// to avoid catching the exception.
[DebuggerHidden]
public static bool ReportAndPropagateUnlessCanceled(Exception exception)
{
@@ -96,15 +100,27 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception)
}
///
- /// Use in an exception filter to report a fatal error.
- /// Calls unless the operation has been cancelled.
- /// The exception is passed through (the method returns false).
+ /// Use in an exception filter to report a fatal error (by calling ), unless the
+ /// operation has been cancelled at the request of . The exception is
+ /// never caught.
+ ///
+ /// Cancellable operations are only expected to throw if the
+ /// applicable indicates cancellation is requested by setting
+ /// . Unexpected cancellation, i.e. an
+ /// which occurs without
+ /// requesting cancellation, is treated as an error by this method.
+ ///
+ /// This method does not require to match
+ /// , provided cancellation is expected per the previous
+ /// paragraph.
///
- /// False to avoid catching the exception.
+ /// A which will have
+ /// set if cancellation is expected.
+ /// to avoid catching the exception.
[DebuggerHidden]
- public static bool ReportAndPropagateUnlessCanceled(Exception exception, CancellationToken cancellationToken)
+ public static bool ReportAndPropagateUnlessCanceled(Exception exception, CancellationToken contextCancellationToken)
{
- if (IsCurrentOperationBeingCancelled(exception, cancellationToken))
+ if (IsCurrentOperationBeingCancelled(exception, contextCancellationToken))
{
return false;
}
@@ -113,11 +129,11 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, Cancell
}
///
- /// Use in an exception filter to report a non-fatal error.
- /// Unless the exception is
- /// it calls . The exception isn't passed through (the method returns true).
+ /// Use in an exception filter to report a non-fatal error (by calling ) and catch
+ /// the exception, unless the operation was cancelled.
///
- /// True to catch the exception.
+ /// to catch the exception if the non-fatal error was reported; otherwise,
+ /// to propagate the exception if the operation was cancelled.
[DebuggerHidden]
public static bool ReportAndCatchUnlessCanceled(Exception exception)
{
@@ -130,15 +146,28 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception)
}
///
- /// Use in an exception filter to report a non-fatal error.
- /// Calls unless the operation has been cancelled.
- /// The exception isn't passed through (the method returns true).
+ /// Use in an exception filter to report a non-fatal error (by calling ) and
+ /// catch the exception, unless the operation was cancelled at the request of
+ /// .
+ ///
+ /// Cancellable operations are only expected to throw if the
+ /// applicable indicates cancellation is requested by setting
+ /// . Unexpected cancellation, i.e. an
+ /// which occurs without
+ /// requesting cancellation, is treated as an error by this method.
+ ///
+ /// This method does not require to match
+ /// , provided cancellation is expected per the previous
+ /// paragraph.
///
- /// True to catch the exception.
+ /// A which will have
+ /// set if cancellation is expected.
+ /// to catch the exception if the non-fatal error was reported; otherwise,
+ /// to propagate the exception if the operation was cancelled.
[DebuggerHidden]
- public static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken cancellationToken)
+ public static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken contextCancellationToken)
{
- if (IsCurrentOperationBeingCancelled(exception, cancellationToken))
+ if (IsCurrentOperationBeingCancelled(exception, contextCancellationToken))
{
return false;
}
@@ -147,10 +176,10 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception, Cancellatio
}
///
- /// Use in an exception filter to report a fatal error.
- /// Calls and passes the exception through (the method returns false).
+ /// Use in an exception filter to report a fatal error without catching the exception.
+ /// The error is reported by calling .
///
- /// False to avoid catching the exception.
+ /// to avoid catching the exception.
[DebuggerHidden]
public static bool ReportAndPropagate(Exception exception)
{
diff --git a/src/Compilers/Core/Portable/InternalUtilities/NoThrowStreamDisposer.cs b/src/Compilers/Core/Portable/InternalUtilities/NoThrowStreamDisposer.cs
index faefc87654f3e..959db5f897c1e 100644
--- a/src/Compilers/Core/Portable/InternalUtilities/NoThrowStreamDisposer.cs
+++ b/src/Compilers/Core/Portable/InternalUtilities/NoThrowStreamDisposer.cs
@@ -27,7 +27,7 @@ internal class NoThrowStreamDisposer : IDisposable
public Stream Stream { get; }
///
- /// True iff an exception was thrown during a call to
+ /// True if and only if an exception was thrown during a call to
///
public bool HasFailedToDispose
{
diff --git a/src/Compilers/Core/Portable/MetadataReference/MetadataReference.cs b/src/Compilers/Core/Portable/MetadataReference/MetadataReference.cs
index 748468b7d3217..76d3d5226fc8a 100644
--- a/src/Compilers/Core/Portable/MetadataReference/MetadataReference.cs
+++ b/src/Compilers/Core/Portable/MetadataReference/MetadataReference.cs
@@ -228,9 +228,18 @@ public static PortableExecutableReference CreateFromFile(
string path,
MetadataReferenceProperties properties = default,
DocumentationProvider? documentation = null)
- {
- var peStream = FileUtilities.OpenFileStream(path);
+ => CreateFromFile(
+ StandardFileSystem.Instance.OpenFileWithNormalizedException(path, FileMode.Open, FileAccess.Read, FileShare.Read),
+ path,
+ properties,
+ documentation);
+ internal static PortableExecutableReference CreateFromFile(
+ Stream peStream,
+ string path,
+ MetadataReferenceProperties properties = default,
+ DocumentationProvider? documentation = null)
+ {
// prefetch image, close stream to avoid locking it:
var module = ModuleMetadata.CreateFromStream(peStream, PEStreamOptions.PrefetchEntireImage);
@@ -324,7 +333,7 @@ internal static PortableExecutableReference CreateFromAssemblyInternal(
throw new NotSupportedException(CodeAnalysisResources.CantCreateReferenceToAssemblyWithoutLocation);
}
- Stream peStream = FileUtilities.OpenFileStream(location);
+ Stream peStream = StandardFileSystem.Instance.OpenFileWithNormalizedException(location, FileMode.Open, FileAccess.Read, FileShare.Read);
// The file is locked by the CLR assembly loader, so we can create a lazily read metadata,
// which might also lock the file until the reference is GC'd.
diff --git a/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs b/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs
index 166240066474b..90ecda00c8244 100644
--- a/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs
+++ b/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs
@@ -195,7 +195,7 @@ public static ModuleMetadata CreateFromStream(Stream peStream, PEStreamOptions o
/// Reading from a file path is not supported by the platform.
public static ModuleMetadata CreateFromFile(string path)
{
- return CreateFromStream(FileUtilities.OpenFileStream(path));
+ return CreateFromStream(StandardFileSystem.Instance.OpenFileWithNormalizedException(path, FileMode.Open, FileAccess.Read, FileShare.Read));
}
///
diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj
index 410dd75511424..89f574eceeac0 100644
--- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj
+++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj
@@ -52,7 +52,7 @@
-
+
diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraph.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraph.cs
index 72290a4baabdb..7661a7f470fd8 100644
--- a/src/Compilers/Core/Portable/Operations/ControlFlowGraph.cs
+++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraph.cs
@@ -183,7 +183,7 @@ internal static ControlFlowGraph CreateCore(IOperation operation, string argumen
Debug.Assert(controlFlowGraph.OriginalOperation == operation);
return controlFlowGraph;
}
- catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
{
// Log a Non-fatal-watson and then ignore the crash in the attempt of getting flow graph.
Debug.Assert(false, "\n" + e.ToString());
diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs
index 079ee33a28268..d405f7e0ff4be 100644
--- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs
+++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs
@@ -883,7 +883,7 @@ private void EmbedCompilationOptions(BlobReader? pdbCompilationOptionsReader, Co
var optimizationLevel = module.CommonCompilation.Options.OptimizationLevel;
var debugPlusMode = module.CommonCompilation.Options.DebugPlusMode;
- if (optimizationLevel != OptimizationLevel.Debug || debugPlusMode)
+ if ((optimizationLevel, debugPlusMode) != OptimizationLevelFacts.DefaultValues)
{
WriteValue(CompilationOptionNames.Optimization, optimizationLevel.ToPdbSerializedString(debugPlusMode));
}
diff --git a/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs b/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs
index 19e1d41ce38b8..df17c3dcf1b13 100644
--- a/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs
+++ b/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs
@@ -12,7 +12,7 @@
using Microsoft.CodeAnalysis.Text;
using CS = Microsoft.CodeAnalysis.CSharp;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public sealed class CSharpCompilationFactory : CompilationFactory
{
diff --git a/src/Compilers/Core/Rebuild/CompilationFactory.cs b/src/Compilers/Core/Rebuild/CompilationFactory.cs
index c09e60a752abf..573d37f2a74f9 100644
--- a/src/Compilers/Core/Rebuild/CompilationFactory.cs
+++ b/src/Compilers/Core/Rebuild/CompilationFactory.cs
@@ -12,7 +12,7 @@
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public abstract class CompilationFactory
{
@@ -126,14 +126,20 @@ public unsafe EmitResult Emit(
}
}
- protected static (OptimizationLevel, bool) GetOptimizationLevel(string? optimizationLevel)
- => optimizationLevel switch
+ protected static (OptimizationLevel OptimizationLevel, bool DebugPlus) GetOptimizationLevel(string? value)
+ {
+ if (value is null)
{
- null or "debug" => (OptimizationLevel.Debug, false),
- "debug-plus" => (OptimizationLevel.Debug, true),
- "release" => (OptimizationLevel.Release, false),
- _ => throw new InvalidDataException($"Optimization \"{optimizationLevel}\" level not recognized")
- };
+ return OptimizationLevelFacts.DefaultValues;
+ }
+
+ if (!OptimizationLevelFacts.TryParsePdbSerializedString(value, out OptimizationLevel optimizationLevel, out bool debugPlus))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return (optimizationLevel, debugPlus);
+ }
protected static Platform GetPlatform(string? platform)
=> platform is null
diff --git a/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs b/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs
index d07e4e06ebc79..1186105c49bbb 100644
--- a/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs
+++ b/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs
@@ -20,7 +20,7 @@
using Microsoft.Extensions.Logging;
using Roslyn.Utilities;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public class CompilationOptionsReader
{
diff --git a/src/Compilers/Core/Rebuild/Extensions.cs b/src/Compilers/Core/Rebuild/Extensions.cs
index 296ca185e880b..0bca8051fcc8f 100644
--- a/src/Compilers/Core/Rebuild/Extensions.cs
+++ b/src/Compilers/Core/Rebuild/Extensions.cs
@@ -8,7 +8,7 @@
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public static class Extensions
{
diff --git a/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs b/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs
index 3c3e9d5b4c8f0..f46b9f67c7900 100644
--- a/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs
+++ b/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs
@@ -8,7 +8,7 @@
using System;
using Microsoft.Extensions.Logging;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
internal class MetadataCompilationOptions
{
diff --git a/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs b/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs
index 7693f69238046..f0a9b13b9739a 100644
--- a/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs
+++ b/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs
@@ -7,7 +7,7 @@
using System.IO;
using Microsoft.CodeAnalysis;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public readonly struct MetadataReferenceInfo
{
diff --git a/src/Compilers/Core/Rebuild/Rebuild.csproj b/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj
similarity index 94%
rename from src/Compilers/Core/Rebuild/Rebuild.csproj
rename to src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj
index b1b38256ba4ec..b56ea247ad62d 100644
--- a/src/Compilers/Core/Rebuild/Rebuild.csproj
+++ b/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj
@@ -4,6 +4,7 @@
Library
+ Microsoft.CodeAnalysis.Rebuild
netcoreapp3.1;netstandard2.0
AnyCPU
enable
diff --git a/src/Compilers/Core/Rebuild/Records.cs b/src/Compilers/Core/Rebuild/Records.cs
index 62a525c94a5d4..38fa2411ec403 100644
--- a/src/Compilers/Core/Rebuild/Records.cs
+++ b/src/Compilers/Core/Rebuild/Records.cs
@@ -7,6 +7,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
}
diff --git a/src/Compilers/Core/Rebuild/SourceFileInfo.cs b/src/Compilers/Core/Rebuild/SourceFileInfo.cs
index 5f6cfbca06398..180f0c7a9d5f5 100644
--- a/src/Compilers/Core/Rebuild/SourceFileInfo.cs
+++ b/src/Compilers/Core/Rebuild/SourceFileInfo.cs
@@ -4,7 +4,7 @@
using Microsoft.CodeAnalysis.Text;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public readonly struct SourceFileInfo
{
diff --git a/src/Compilers/Core/Rebuild/SourceLink.cs b/src/Compilers/Core/Rebuild/SourceLink.cs
index 7c23433e65233..39764edbe5a7d 100644
--- a/src/Compilers/Core/Rebuild/SourceLink.cs
+++ b/src/Compilers/Core/Rebuild/SourceLink.cs
@@ -6,7 +6,7 @@
using System.Collections.Generic;
using System.Text;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
/// An entry in the source-link.json dictionary.
public readonly struct SourceLink
diff --git a/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs b/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs
index 10b7dfb606f88..8f467e7c4f4ef 100644
--- a/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs
+++ b/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs
@@ -15,7 +15,7 @@
using Roslyn.Utilities;
using VB = Microsoft.CodeAnalysis.VisualBasic;
-namespace BuildValidator
+namespace Microsoft.CodeAnalysis.Rebuild
{
public sealed class VisualBasicCompilationFactory : CompilationFactory
{
diff --git a/src/Compilers/Core/RebuildTest/CSharpRebuildTests.cs b/src/Compilers/Core/RebuildTest/CSharpRebuildTests.cs
index be91d5d5ae955..c2cd09dbd34a8 100644
--- a/src/Compilers/Core/RebuildTest/CSharpRebuildTests.cs
+++ b/src/Compilers/Core/RebuildTest/CSharpRebuildTests.cs
@@ -5,9 +5,9 @@
using System.Collections.Immutable;
using System.Linq;
using System.Reflection.PortableExecutable;
-using BuildValidator;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Rebuild;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.Extensions.Logging;
using Xunit;
diff --git a/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj b/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj
index 1642cfaf33538..532e1ed841b3d 100644
--- a/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj
+++ b/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj
@@ -7,6 +7,9 @@
true
net5.0;net472
+
+
+
@@ -14,7 +17,7 @@
-
+
diff --git a/src/Compilers/Core/RebuildTest/OptionRoundTripTests.cs b/src/Compilers/Core/RebuildTest/OptionRoundTripTests.cs
index db3c332fd6501..ebdf23eb4e72f 100644
--- a/src/Compilers/Core/RebuildTest/OptionRoundTripTests.cs
+++ b/src/Compilers/Core/RebuildTest/OptionRoundTripTests.cs
@@ -10,12 +10,12 @@
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading;
-using BuildValidator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.Rebuild;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.Extensions.Logging;
using Xunit;
@@ -32,104 +32,6 @@ public class OptionRoundTripTests : CSharpTestBase
public static readonly object[][] Platforms = ((Platform[])Enum.GetValues(typeof(Platform))).Select(p => new[] { (object)p }).ToArray();
- private static void VerifyRoundTrip(TCompilation original)
- where TCompilation : Compilation
- {
- Assert.True(original.SyntaxTrees.All(x => !string.IsNullOrEmpty(x.FilePath)));
- Assert.True(original.Options.Deterministic);
-
- original.VerifyDiagnostics();
- var originalBytes = original.EmitToArray(new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded));
- var originalReader = new PEReader(originalBytes);
- var originalPdbReader = originalReader.GetEmbeddedPdbMetadataReader();
-
- var factory = LoggerFactory.Create(configure => { });
- var logger = factory.CreateLogger("RoundTripVerification");
- var optionsReader = new CompilationOptionsReader(logger, originalPdbReader, originalReader);
- var assemblyFileName = original.AssemblyName!;
- if (typeof(TCompilation) == typeof(CSharpCompilation))
- {
- var assemblyFileExtension = original.Options.OutputKind switch
- {
- OutputKind.DynamicallyLinkedLibrary => ".dll",
- OutputKind.ConsoleApplication => ".exe",
- _ => throw new InvalidOperationException(),
- };
- assemblyFileName += assemblyFileExtension;
- }
-
- var compilationFactory = CompilationFactory.Create(assemblyFileName, optionsReader);
- var rebuild = compilationFactory.CreateCompilation(
- original.SyntaxTrees.Select(x => compilationFactory.CreateSyntaxTree(x.FilePath, x.GetText())).ToImmutableArray(),
- original.References.ToImmutableArray());
-
- Assert.IsType(rebuild);
- VerifyCompilationOptions(original.Options, rebuild.Options);
-
- using var rebuildStream = new MemoryStream();
- var result = compilationFactory.Emit(
- rebuildStream,
- rebuild,
- embeddedTexts: ImmutableArray.Empty,
- CancellationToken.None);
- Assert.Empty(result.Diagnostics);
- Assert.True(result.Success);
- Assert.Equal(originalBytes.ToArray(), rebuildStream.ToArray());
- }
-
-#pragma warning disable 612
- private static void VerifyCompilationOptions(CompilationOptions originalOptions, CompilationOptions rebuildOptions)
- {
- var type = originalOptions.GetType();
- foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
- {
- switch (propertyInfo.Name)
- {
- case nameof(CompilationOptions.GeneralDiagnosticOption):
- case nameof(CompilationOptions.Features):
- case nameof(CompilationOptions.ModuleName):
- case nameof(CompilationOptions.MainTypeName):
- case nameof(CompilationOptions.ConcurrentBuild):
- case nameof(CompilationOptions.WarningLevel):
- // Can be different and are special cased
- break;
- case nameof(VisualBasicCompilationOptions.ParseOptions):
- {
- var originalValue = propertyInfo.GetValue(originalOptions)!;
- var rebuildValue = propertyInfo.GetValue(rebuildOptions)!;
- VerifyParseOptions((ParseOptions)originalValue, (ParseOptions)rebuildValue);
- }
- break;
- default:
- {
- var originalValue = propertyInfo.GetValue(originalOptions);
- var rebuildValue = propertyInfo.GetValue(rebuildOptions);
- Assert.Equal(originalValue, rebuildValue);
- }
- break;
- }
- }
- }
-#pragma warning restore 612
-
- private static void VerifyParseOptions(ParseOptions originalOptions, ParseOptions rebuildOptions)
- {
- var type = originalOptions.GetType();
- foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
- {
- // Several options are expected to be different and they are special cased here.
- if (propertyInfo.Name == nameof(VisualBasicParseOptions.SpecifiedLanguageVersion))
- {
- continue;
- }
-
- var originalValue = propertyInfo.GetValue(originalOptions);
- var rebuildValue = propertyInfo.GetValue(rebuildOptions);
-
- Assert.Equal(originalValue, rebuildValue);
- }
- }
-
[Theory]
[MemberData(nameof(Platforms))]
public void Platform_RoundTrip(Platform platform)
@@ -139,7 +41,7 @@ public void Platform_RoundTrip(Platform platform)
options: BaseCSharpCompilationOptions.WithPlatform(platform),
sourceFileName: "test.cs");
- VerifyRoundTrip(original);
+ RoundTripUtil.VerifyRoundTrip(original);
}
[Theory]
@@ -157,7 +59,17 @@ End Sub
assemblyName: "test",
sourceFileName: "test.vb");
- VerifyRoundTrip(original);
+ RoundTripUtil.VerifyRoundTrip(original);
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void OptimizationLevel_ParsePdbSerializedString(OptimizationLevel optimization, bool debugPlus)
+ {
+ var data = OptimizationLevelFacts.ToPdbSerializedString(optimization, debugPlus);
+ Assert.True(OptimizationLevelFacts.TryParsePdbSerializedString(data, out var optimization2, out var debugPlus2));
+ Assert.Equal(optimization, optimization2);
+ Assert.Equal(debugPlus, debugPlus2);
}
}
}
diff --git a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.CSharpRebuildCompiler.cs b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.CSharpRebuildCompiler.cs
new file mode 100644
index 0000000000000..6a5c96f9678be
--- /dev/null
+++ b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.CSharpRebuildCompiler.cs
@@ -0,0 +1,19 @@
+// 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.CSharp;
+
+namespace Microsoft.CodeAnalysis.Rebuild.UnitTests
+{
+ public partial class RebuildCommandLineTests
+ {
+ private sealed class CSharpRebuildCompiler : CSharpCompiler
+ {
+ internal CSharpRebuildCompiler(string[] args)
+ : base(CSharpCommandLineParser.Default, responseFile: null, args, StandardBuildPaths, additionalReferenceDirectories: null, new DefaultAnalyzerAssemblyLoader())
+ {
+ }
+ }
+ }
+}
diff --git a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.VisualBasicRebuildCompiler.cs b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.VisualBasicRebuildCompiler.cs
new file mode 100644
index 0000000000000..8cf958b08aeb1
--- /dev/null
+++ b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.VisualBasicRebuildCompiler.cs
@@ -0,0 +1,19 @@
+// 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.VisualBasic;
+
+namespace Microsoft.CodeAnalysis.Rebuild.UnitTests
+{
+ public partial class RebuildCommandLineTests
+ {
+ private sealed class VisualBasicRebuildCompiler : VisualBasicCompiler
+ {
+ internal VisualBasicRebuildCompiler(string[] args)
+ : base(VisualBasicCommandLineParser.Default, responseFile: null, args, StandardBuildPaths, additionalReferenceDirectories: null, new DefaultAnalyzerAssemblyLoader())
+ {
+ }
+ }
+ }
+}
diff --git a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs
new file mode 100644
index 0000000000000..9f2c7cb7dce37
--- /dev/null
+++ b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs
@@ -0,0 +1,289 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Roslyn.Test.Utilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.CodeAnalysis.Rebuild.UnitTests
+{
+ public sealed partial class RebuildCommandLineTests : CSharpTestBase
+ {
+ private record CommandInfo(string CommandLine, string PeFileName, string? PdbFileName);
+
+ internal static string RootDirectory => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"q:\" : "/";
+ internal static string ClientDirectory => Path.Combine(RootDirectory, "compiler");
+ internal static string WorkingDirectory => Path.Combine(RootDirectory, "rebuild");
+ internal static string SdkDirectory => Path.Combine(RootDirectory, "sdk");
+ internal static string OutputDirectory => Path.Combine(RootDirectory, "output");
+
+ internal static BuildPaths StandardBuildPaths => new BuildPaths(ClientDirectory, WorkingDirectory, SdkDirectory, tempDir: null);
+
+ public ITestOutputHelper TestOutputHelper { get; }
+ public Dictionary FilePathToStreamMap { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public RebuildCommandLineTests(ITestOutputHelper testOutputHelper)
+ {
+ TestOutputHelper = testOutputHelper;
+ }
+
+ private void AddSourceFile(string filePath, string content)
+ {
+ FilePathToStreamMap.Add(Path.Combine(WorkingDirectory, filePath), new TestableFile(content));
+ }
+
+ private void AddReference(string filePath, byte[] imageBytes)
+ {
+ FilePathToStreamMap.Add(Path.Combine(SdkDirectory, filePath), new TestableFile(imageBytes));
+ }
+
+ private void AddOutputFile(ref string? filePath)
+ {
+ if (filePath is object)
+ {
+ filePath = Path.Combine(OutputDirectory, filePath);
+ FilePathToStreamMap.Add(filePath, new TestableFile());
+ }
+ }
+
+ private void VerifyRoundTrip(CommonCompiler commonCompiler, string peFilePath, string? pdbFilePath = null, CancellationToken cancellationToken = default)
+ {
+ Assert.True(commonCompiler.Arguments.CompilationOptions.Deterministic);
+ using var writer = new StringWriter();
+ commonCompiler.FileSystem = TestableFileSystem.CreateForMap(FilePathToStreamMap);
+ var result = commonCompiler.Run(writer, cancellationToken);
+ TestOutputHelper.WriteLine(writer.ToString());
+ Assert.Equal(0, result);
+
+ var peStream = FilePathToStreamMap[peFilePath].GetStream();
+ var pdbStream = pdbFilePath is object ? FilePathToStreamMap[pdbFilePath].GetStream() : null;
+
+ var compilation = commonCompiler.CreateCompilation(
+ writer,
+ touchedFilesLogger: null,
+ errorLoggerOpt: null,
+ analyzerConfigOptions: default,
+ globalConfigOptions: default);
+ AssertEx.NotNull(compilation);
+ RoundTripUtil.VerifyCompilationOptions(commonCompiler.Arguments.CompilationOptions, compilation.Options);
+
+ RoundTripUtil.VerifyRoundTrip(
+ peStream,
+ pdbStream,
+ Path.GetFileName(peFilePath),
+ compilation.SyntaxTrees.ToImmutableArray(),
+ compilation.References.ToImmutableArray());
+ }
+
+ private void AddCSharpSourceFiles()
+ {
+ AddSourceFile("hw.cs", @"
+using System;
+Console.WriteLine(""Hello World"");
+");
+
+ AddSourceFile("lib1.cs", @"
+using System;
+
+class Library
+{
+ void Method()
+ {
+ var lib = new Library();
+ Console.WriteLine(lib);
+ }
+}
+");
+ }
+
+ public static IEnumerable
-
-
diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs
index e83e493d3a787..739189b0e9308 100644
--- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs
+++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs
@@ -53,6 +53,15 @@ private ReadOnlyCollection CreateDescriptionItems()
}
var sourceText = document.GetTextSynchronously(CancellationToken.None);
+ var item = _searchResult.NavigableItem;
+ var spanStart = item.SourceSpan.Start;
+ if (item.IsStale && spanStart > sourceText.Length)
+ {
+ // in the case of a stale item, the span may be out of bounds of the document. Cap
+ // us to the end of the document as that's where we're going to navigate the user
+ // to.
+ spanStart = sourceText.Length;
+ }
var items = new List
{
@@ -70,7 +79,7 @@ private ReadOnlyCollection CreateDescriptionItems()
new ReadOnlyCollection(
new[] { new DescriptionRun("Line:", bold: true) }),
new ReadOnlyCollection(
- new[] { new DescriptionRun((sourceText.Lines.IndexOf(_searchResult.NavigableItem.SourceSpan.Start) + 1).ToString()) }))
+ new[] { new DescriptionRun((sourceText.Lines.IndexOf(spanStart) + 1).ToString()) }))
};
var summary = _searchResult.Summary;
@@ -102,11 +111,22 @@ public void NavigateTo()
var workspace = document.Project.Solution.Workspace;
var navigationService = workspace.Services.GetService();
- // Document tabs opened by NavigateTo are carefully created as preview or regular
- // tabs by them; trying to specifically open them in a particular kind of tab here
- // has no effect.
- // TODO: Get the platform to use and pass us an operation context, or create one ourselves.
- navigationService.TryNavigateToSpan(workspace, document.Id, _searchResult.NavigableItem.SourceSpan, CancellationToken.None);
+ // Document tabs opened by NavigateTo are carefully created as preview or regular tabs
+ // by them; trying to specifically open them in a particular kind of tab here has no
+ // effect.
+ //
+ // In the case of a stale item, don't require that the span be in bounds of the document
+ // as it exists right now.
+ //
+ // TODO: Get the platform to use and pass us an operation context, or create one
+ // ourselves.
+ navigationService.TryNavigateToSpan(
+ workspace,
+ document.Id,
+ _searchResult.NavigableItem.SourceSpan,
+ options: null,
+ allowInvalidSpan: _searchResult.NavigableItem.IsStale,
+ CancellationToken.None);
}
public int GetProvisionalViewingStatus()
diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs
index 65543854159ca..8dd3316a1bfd4 100644
--- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs
+++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Controller.Session_ComputeModel.cs
@@ -125,7 +125,7 @@ private async Task ComputeModelInBackgroundAsync(
.WithSelectedParameter(selection.SelectedParameter);
}
}
- catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable;
}
@@ -216,7 +216,7 @@ private static bool CompareParts(TaggedText p1, TaggedText p2)
return (bestProvider, bestItems);
}
- catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e))
+ catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
{
return (null, null);
}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/ISettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/ISettingsAggregator.cs
new file mode 100644
index 0000000000000..74520e4ac98dc
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/ISettingsAggregator.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.CodeAnalysis.Editor.EditorConfigSettings.DataProvider;
+using Microsoft.CodeAnalysis.Host;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings
+{
+ internal interface ISettingsAggregator : IWorkspaceService
+ {
+ ISettingsProvider? GetSettingsProvider(string fileName);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs
new file mode 100644
index 0000000000000..144b853d9abd9
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs
@@ -0,0 +1,99 @@
+// 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 Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings
+{
+ internal partial class SettingsAggregator : ISettingsAggregator
+ {
+ private readonly Workspace _workspace;
+ private readonly ISettingsProviderFactory _analyzerProvider;
+ private ISettingsProviderFactory _formattingProvider;
+ private ISettingsProviderFactory _codeStyleProvider;
+
+ public SettingsAggregator(Workspace workspace)
+ {
+ _workspace = workspace;
+ _workspace.WorkspaceChanged += UpdateProviders;
+ _formattingProvider = GetOptionsProviderFactory(_workspace);
+ _codeStyleProvider = GetOptionsProviderFactory(_workspace);
+ _analyzerProvider = GetOptionsProviderFactory(_workspace);
+ }
+
+ private void UpdateProviders(object? sender, WorkspaceChangeEventArgs e)
+ {
+ switch (e.Kind)
+ {
+ case WorkspaceChangeKind.SolutionChanged:
+ case WorkspaceChangeKind.SolutionAdded:
+ case WorkspaceChangeKind.SolutionRemoved:
+ case WorkspaceChangeKind.SolutionCleared:
+ case WorkspaceChangeKind.SolutionReloaded:
+ case WorkspaceChangeKind.ProjectAdded:
+ case WorkspaceChangeKind.ProjectRemoved:
+ case WorkspaceChangeKind.ProjectChanged:
+ _formattingProvider = GetOptionsProviderFactory(_workspace);
+ _codeStyleProvider = GetOptionsProviderFactory(_workspace);
+ break;
+ default:
+ break;
+ }
+ }
+
+ public ISettingsProvider? GetSettingsProvider(string fileName)
+ {
+ if (typeof(TData) == typeof(AnalyzerSetting))
+ {
+ return (ISettingsProvider)_analyzerProvider.GetForFile(fileName);
+ }
+
+ if (typeof(TData) == typeof(FormattingSetting))
+ {
+ return (ISettingsProvider)_formattingProvider.GetForFile(fileName);
+ }
+
+ if (typeof(TData) == typeof(CodeStyleSetting))
+ {
+ return (ISettingsProvider)_codeStyleProvider.GetForFile(fileName);
+ }
+
+ return null;
+ }
+
+ private static ISettingsProviderFactory GetOptionsProviderFactory(Workspace workspace)
+ {
+ var providers = new List>();
+ var commonProvider = workspace.Services.GetRequiredService>();
+ providers.Add(commonProvider);
+ var solution = workspace.CurrentSolution;
+ var supportsCSharp = solution.Projects.Any(p => p.Language.Equals(LanguageNames.CSharp, StringComparison.OrdinalIgnoreCase));
+ var supportsVisualBasic = solution.Projects.Any(p => p.Language.Equals(LanguageNames.VisualBasic, StringComparison.OrdinalIgnoreCase));
+ if (supportsCSharp)
+ {
+ TryAddProviderForLanguage(LanguageNames.CSharp, workspace, providers);
+ }
+ if (supportsVisualBasic)
+ {
+ TryAddProviderForLanguage(LanguageNames.VisualBasic, workspace, providers);
+ }
+
+ return new CombinedOptionsProviderFactory(providers.ToImmutableArray());
+
+ static void TryAddProviderForLanguage(string language, Workspace workspace, List> providers)
+ {
+ var provider = workspace.Services.GetLanguageServices(language).GetService>();
+ if (provider is not null)
+ {
+ providers.Add(provider);
+ }
+ }
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregatorFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregatorFactory.cs
new file mode 100644
index 0000000000000..87a8598b69e32
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregatorFactory.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.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings
+{
+
+ [ExportWorkspaceServiceFactory(typeof(ISettingsAggregator), ServiceLayer.Default), Shared]
+ internal class SettingsAggregatorFactory : IWorkspaceServiceFactory
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public SettingsAggregatorFactory()
+ {
+ }
+
+ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
+ => new SettingsAggregator(workspaceServices.Workspace);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs
new file mode 100644
index 0000000000000..98cef0b4f22be
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs
@@ -0,0 +1,57 @@
+// 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.Globalization;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal class AnalyzerSetting
+ {
+ private readonly DiagnosticDescriptor _descriptor;
+ private readonly AnalyzerSettingsUpdater _settingsUpdater;
+
+ public AnalyzerSetting(DiagnosticDescriptor descriptor,
+ ReportDiagnostic effectiveSeverity,
+ AnalyzerSettingsUpdater settingsUpdater,
+ Language language)
+ {
+ _descriptor = descriptor;
+ _settingsUpdater = settingsUpdater;
+ DiagnosticSeverity severity = default;
+ if (effectiveSeverity == ReportDiagnostic.Default)
+ {
+ severity = descriptor.DefaultSeverity;
+ }
+ else if (effectiveSeverity.ToDiagnosticSeverity() is DiagnosticSeverity severity1)
+ {
+ severity = severity1;
+ }
+
+ var enabled = effectiveSeverity != ReportDiagnostic.Suppress;
+ IsEnabled = enabled;
+ Severity = severity;
+ Language = language;
+ }
+
+ public string Id => _descriptor.Id;
+ public string Title => _descriptor.Title.ToString(CultureInfo.CurrentCulture);
+ public string Description => _descriptor.Description.ToString(CultureInfo.CurrentCulture);
+ public string Category => _descriptor.Category;
+ public DiagnosticSeverity Severity { get; private set; }
+ public bool IsEnabled { get; private set; }
+ public Language Language { get; }
+
+ internal void ChangeSeverity(DiagnosticSeverity severity)
+ {
+ if (severity == Severity)
+ return;
+
+ Severity = severity;
+ _ = _settingsUpdater.QueueUpdateAsync(this, severity);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs
new file mode 100644
index 0000000000000..907245d073918
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs
@@ -0,0 +1,57 @@
+// 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.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ private class BooleanCodeStyleSetting : BooleanCodeStyleSettingBase
+ {
+ private readonly Option2> _option;
+ private readonly AnalyzerConfigOptions _editorConfigOptions;
+ private readonly OptionSet _visualStudioOptions;
+
+ public BooleanCodeStyleSetting(Option2> option,
+ string description,
+ string? trueValueDescription,
+ string? falseValueDescription,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ : base(description, option.Group.Description, trueValueDescription, falseValueDescription, updater)
+ {
+ _option = option;
+ _editorConfigOptions = editorConfigOptions;
+ _visualStudioOptions = visualStudioOptions;
+ }
+
+ public override bool IsDefinedInEditorConfig => _editorConfigOptions.TryGetEditorConfigOption>(_option, out _);
+
+ protected override void ChangeSeverity(NotificationOption2 severity)
+ {
+ var option = GetOption();
+ option.Notification = severity;
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ public override void ChangeValue(int valueIndex)
+ {
+ var value = valueIndex == 0;
+ var option = GetOption();
+ option.Value = value;
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ protected override CodeStyleOption2 GetOption()
+ => _editorConfigOptions.TryGetEditorConfigOption(_option, out CodeStyleOption2? value) && value is not null
+ ? value
+ : _visualStudioOptions.GetOption(_option);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSettingBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSettingBase.cs
new file mode 100644
index 0000000000000..bd2e3822b24b7
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSettingBase.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ private abstract class BooleanCodeStyleSettingBase : CodeStyleSetting
+ {
+ private readonly string _trueValueDescription;
+ private readonly string _falseValueDescription;
+
+ public BooleanCodeStyleSettingBase(string description,
+ string category,
+ string? trueValueDescription,
+ string? falseValueDescription,
+ OptionUpdater updater)
+ : base(description, updater)
+ {
+ Category = category;
+ _trueValueDescription = trueValueDescription ?? EditorFeaturesResources.Yes;
+ _falseValueDescription = falseValueDescription ?? EditorFeaturesResources.No;
+ }
+
+ public override string Category { get; }
+ public override Type Type => typeof(bool);
+ public override DiagnosticSeverity Severity => GetOption().Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden;
+ public override string GetCurrentValue() => GetOption().Value ? _trueValueDescription : _falseValueDescription;
+ public override object? Value => GetOption().Value;
+ public override string[] GetValues() => new[] { _trueValueDescription, _falseValueDescription };
+ protected abstract CodeStyleOption2 GetOption();
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs
new file mode 100644
index 0000000000000..8506d092f0315
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ private class EnumCodeStyleSetting : EnumCodeStyleSettingBase
+ where T : Enum
+ {
+ private readonly Option2> _option;
+ private readonly AnalyzerConfigOptions _editorConfigOptions;
+ private readonly OptionSet _visualStudioOptions;
+
+ public EnumCodeStyleSetting(Option2> option,
+ string description,
+ T[] enumValues,
+ string[] valueDescriptions,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ : base(description, enumValues, valueDescriptions, option.Group.Description, updater)
+ {
+ _option = option;
+ _editorConfigOptions = editorConfigOptions;
+ _visualStudioOptions = visualStudioOptions;
+ }
+
+ public override bool IsDefinedInEditorConfig => _editorConfigOptions.TryGetEditorConfigOption>(_option, out _);
+
+ protected override void ChangeSeverity(NotificationOption2 severity)
+ {
+ var option = GetOption();
+ option.Notification = severity;
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ public override void ChangeValue(int valueIndex)
+ {
+ var option = GetOption();
+ option.Value = _enumValues[valueIndex];
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ protected override CodeStyleOption2 GetOption()
+ => _editorConfigOptions.TryGetEditorConfigOption(_option, out CodeStyleOption2? value) && value is not null
+ ? value
+ : _visualStudioOptions.GetOption(_option);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.cs
new file mode 100644
index 0000000000000..8856236da98cc
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSettingBase.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.
+
+using System;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Utilities;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ private abstract class EnumCodeStyleSettingBase : CodeStyleSetting
+ where T : Enum
+ {
+ protected readonly T[] _enumValues;
+ private readonly string[] _valueDescriptions;
+
+ public EnumCodeStyleSettingBase(string description,
+ T[] enumValues,
+ string[] valueDescriptions,
+ string category,
+ OptionUpdater updater)
+ : base(description, updater)
+ {
+ if (enumValues.Length != valueDescriptions.Length)
+ {
+ throw new InvalidOperationException("Values and descriptions must have matching number of elements");
+ }
+ _enumValues = enumValues;
+ _valueDescriptions = valueDescriptions;
+ Category = category;
+ }
+
+ public override string Category { get; }
+ public override Type Type => typeof(T);
+ public override string GetCurrentValue() => _valueDescriptions[_enumValues.IndexOf(GetOption().Value)];
+ public override object? Value => GetOption().Value;
+ public override DiagnosticSeverity Severity => GetOption().Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden;
+ public override string[] GetValues() => _valueDescriptions;
+ protected abstract CodeStyleOption2 GetOption();
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs
new file mode 100644
index 0000000000000..b71d06986e1a5
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs
@@ -0,0 +1,57 @@
+// 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.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ private class PerLanguageBooleanCodeStyleSetting : BooleanCodeStyleSettingBase
+ {
+ private readonly PerLanguageOption2> _option;
+ private readonly AnalyzerConfigOptions _editorConfigOptions;
+ private readonly OptionSet _visualStudioOptions;
+
+ public PerLanguageBooleanCodeStyleSetting(PerLanguageOption2> option,
+ string description,
+ string? trueValueDescription,
+ string? falseValueDescription,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ : base(description, option.Group.Description, trueValueDescription, falseValueDescription, updater)
+ {
+ _option = option;
+ _editorConfigOptions = editorConfigOptions;
+ _visualStudioOptions = visualStudioOptions;
+ }
+
+ public override bool IsDefinedInEditorConfig => _editorConfigOptions.TryGetEditorConfigOption>(_option, out _);
+
+ protected override void ChangeSeverity(NotificationOption2 severity)
+ {
+ var option = GetOption();
+ option.Notification = severity;
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ public override void ChangeValue(int valueIndex)
+ {
+ var value = valueIndex == 0;
+ var option = GetOption();
+ option.Value = value;
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ protected override CodeStyleOption2 GetOption()
+ => _editorConfigOptions.TryGetEditorConfigOption(_option, out CodeStyleOption2? value) && value is not null
+ ? value
+ : _visualStudioOptions.GetOption>(new OptionKey2(_option, LanguageNames.CSharp));
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs
new file mode 100644
index 0000000000000..df33ae0c833e6
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ private class PerLanguageEnumCodeStyleSetting : EnumCodeStyleSettingBase
+ where T : Enum
+ {
+ private readonly PerLanguageOption2> _option;
+ private readonly AnalyzerConfigOptions _editorConfigOptions;
+ private readonly OptionSet _visualStudioOptions;
+
+ public PerLanguageEnumCodeStyleSetting(PerLanguageOption2> option,
+ string description,
+ T[] enumValues,
+ string[] valueDescriptions,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ : base(description, enumValues, valueDescriptions, option.Group.Description, updater)
+ {
+ _option = option;
+ _editorConfigOptions = editorConfigOptions;
+ _visualStudioOptions = visualStudioOptions;
+ }
+
+ public override bool IsDefinedInEditorConfig => _editorConfigOptions.TryGetEditorConfigOption>(_option, out _);
+
+ protected override void ChangeSeverity(NotificationOption2 severity)
+ {
+ var option = GetOption();
+ option.Notification = severity;
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ public override void ChangeValue(int valueIndex)
+ {
+ var option = GetOption();
+ option.Value = _enumValues[valueIndex];
+ _ = Updater.QueueUpdateAsync(_option, option);
+ }
+
+ protected override CodeStyleOption2 GetOption()
+ => _editorConfigOptions.TryGetEditorConfigOption(_option, out CodeStyleOption2? value) && value is not null
+ ? value
+ // TODO(jmarolf): Should we expose duplicate options if the user has a different setting in VB vs. C#?
+ // Today this code will choose whatever option is set for C# as the default.
+ : _visualStudioOptions.GetOption>(new OptionKey2(_option, LanguageNames.CSharp));
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.cs
new file mode 100644
index 0000000000000..c00bb71b00617
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.cs
@@ -0,0 +1,96 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract partial class CodeStyleSetting
+ {
+ public string Description { get; }
+
+ protected readonly OptionUpdater Updater;
+
+ public abstract string Category { get; }
+ public abstract object? Value { get; }
+ public abstract Type Type { get; }
+ public abstract string[] GetValues();
+ public abstract string GetCurrentValue();
+ public abstract DiagnosticSeverity Severity { get; }
+ public abstract bool IsDefinedInEditorConfig { get; }
+
+ public CodeStyleSetting(string description, OptionUpdater updater)
+ {
+ Description = description;
+ Updater = updater;
+ }
+
+ public void ChangeSeverity(DiagnosticSeverity severity)
+ {
+ var notification = severity switch
+ {
+ DiagnosticSeverity.Hidden => NotificationOption2.Silent,
+ DiagnosticSeverity.Info => NotificationOption2.Suggestion,
+ DiagnosticSeverity.Warning => NotificationOption2.Warning,
+ DiagnosticSeverity.Error => NotificationOption2.Error,
+ _ => NotificationOption2.None,
+ };
+
+ ChangeSeverity(notification);
+ }
+
+ protected abstract void ChangeSeverity(NotificationOption2 severity);
+ public abstract void ChangeValue(int valueIndex);
+
+ internal static CodeStyleSetting Create(Option2> option,
+ string description,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater,
+ string? trueValueDescription = null,
+ string? falseValueDescription = null)
+ {
+ return new BooleanCodeStyleSetting(option, description, trueValueDescription, falseValueDescription, editorConfigOptions, visualStudioOptions, updater);
+ }
+
+ internal static CodeStyleSetting Create(PerLanguageOption2> option,
+ string description,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater,
+ string? trueValueDescription = null,
+ string? falseValueDescription = null)
+ {
+ return new PerLanguageBooleanCodeStyleSetting(option, description, trueValueDescription, falseValueDescription, editorConfigOptions, visualStudioOptions, updater);
+ }
+
+ internal static CodeStyleSetting Create(Option2> option,
+ string description,
+ T[] enumValues,
+ string[] valueDescriptions,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ where T : Enum
+ {
+ return new EnumCodeStyleSetting(option, description, enumValues, valueDescriptions, editorConfigOptions, visualStudioOptions, updater);
+ }
+
+ internal static CodeStyleSetting Create(PerLanguageOption2> option,
+ string description,
+ T[] enumValues,
+ string[] valueDescriptions,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ where T : Enum
+ {
+ return new PerLanguageEnumCodeStyleSetting(option, description, enumValues, valueDescriptions, editorConfigOptions, visualStudioOptions, updater);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting.cs
new file mode 100644
index 0000000000000..a013f3ad40551
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal abstract class FormattingSetting
+ {
+ protected OptionUpdater Updater { get; }
+ protected string? Language { get; }
+
+ protected FormattingSetting(string description, OptionUpdater updater, string? language = null)
+ {
+ Description = description ?? throw new ArgumentNullException(nameof(description));
+ Updater = updater;
+ Language = language;
+ }
+
+ public string Description { get; }
+ public abstract string Category { get; }
+ public abstract Type Type { get; }
+ public abstract OptionKey2 Key { get; }
+ public abstract void SetValue(object value);
+ public abstract object? GetValue();
+ public abstract bool IsDefinedInEditorConfig { get; }
+
+ public static PerLanguageFormattingSetting Create(PerLanguageOption2 option,
+ string description,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ where TOption : notnull
+ {
+ return new PerLanguageFormattingSetting(option, description, editorConfigOptions, visualStudioOptions, updater);
+ }
+
+ public static FormattingSetting Create(Option2 option,
+ string description,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ where TOption : struct
+ {
+ return new FormattingSetting(option, description, editorConfigOptions, visualStudioOptions, updater);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs
new file mode 100644
index 0000000000000..0fcfecad99b36
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal sealed class FormattingSetting : FormattingSetting
+ where T : notnull
+ {
+ public override bool IsDefinedInEditorConfig => _options.TryGetEditorConfigOption(_option, out _);
+
+ private bool _isValueSet;
+ private T? _value;
+ public T Value
+ {
+ private set
+ {
+ if (!_isValueSet)
+ {
+ _isValueSet = true;
+ }
+
+ _value = value;
+ }
+ get
+ {
+ if (_value is not null && _isValueSet)
+ {
+ return _value;
+ }
+
+ if (_options.TryGetEditorConfigOption(_option, out T? value) &&
+ value is not null)
+ {
+ return value;
+ }
+
+ return _visualStudioOptions.GetOption(_option);
+ }
+ }
+
+ public override Type Type => typeof(T);
+ public override string Category => _option.Group.Description;
+
+ public override OptionKey2 Key => new(_option, _option.OptionDefinition.IsPerLanguage ? Language ?? LanguageNames.CSharp : null);
+
+ private readonly Option2 _option;
+ private readonly AnalyzerConfigOptions _options;
+ private readonly OptionSet _visualStudioOptions;
+
+ public FormattingSetting(Option2 option,
+ string description,
+ AnalyzerConfigOptions options,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ : base(description, updater)
+ {
+ _option = option;
+ _options = options;
+ _visualStudioOptions = visualStudioOptions;
+ }
+
+ public override void SetValue(object value)
+ {
+ Value = (T)value;
+ _ = Updater.QueueUpdateAsync(_option, value);
+ }
+
+ public override object? GetValue() => Value;
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs
new file mode 100644
index 0000000000000..aebf191e07ba2
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data
+{
+ internal sealed class PerLanguageFormattingSetting : FormattingSetting
+ where T : notnull
+ {
+ private bool _isValueSet;
+ private T? _value;
+ public T Value
+ {
+ private set
+ {
+ if (!_isValueSet)
+ {
+ _isValueSet = true;
+ }
+
+ _value = value;
+ }
+ get
+ {
+ if (_value is not null && _isValueSet)
+ {
+ return _value;
+ }
+
+ if (_editorConfigOptions.TryGetEditorConfigOption(_option, out T? value) &&
+ value is not null)
+ {
+ return value;
+ }
+
+ return (T)_visualStudioOptions.GetOption(Key)!;
+ }
+ }
+
+ private readonly PerLanguageOption2 _option;
+ private readonly AnalyzerConfigOptions _editorConfigOptions;
+ private readonly OptionSet _visualStudioOptions;
+
+ public PerLanguageFormattingSetting(PerLanguageOption2 option,
+ string description,
+ AnalyzerConfigOptions editorConfigOptions,
+ OptionSet visualStudioOptions,
+ OptionUpdater updater)
+ : base(description, updater)
+ {
+ _option = option;
+ _editorConfigOptions = editorConfigOptions;
+ _visualStudioOptions = visualStudioOptions;
+ }
+
+ public override string Category => _option.Group.Description;
+ public override Type Type => typeof(T);
+
+ public override OptionKey2 Key => new(_option, _option.OptionDefinition.IsPerLanguage ? Language ?? LanguageNames.CSharp : null);
+
+ public override bool IsDefinedInEditorConfig => _editorConfigOptions.TryGetEditorConfigOption(_option, out _);
+
+ public override void SetValue(object value)
+ {
+ Value = (T)value;
+ _ = Updater.QueueUpdateAsync(_option, value);
+ }
+
+ public override object? GetValue() => Value;
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs
new file mode 100644
index 0000000000000..61f2157801f12
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs
@@ -0,0 +1,88 @@
+// 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.Tasks;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyzer
+{
+ internal class AnalyzerSettingsProvider : SettingsProviderBase
+ {
+ private readonly IDiagnosticAnalyzerService _analyzerService;
+
+ public AnalyzerSettingsProvider(string fileName, AnalyzerSettingsUpdater settingsUpdater, Workspace workspace, IDiagnosticAnalyzerService analyzerService)
+ : base(fileName, settingsUpdater, workspace)
+ {
+ _analyzerService = analyzerService;
+ Update();
+ }
+
+ protected override void UpdateOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet _)
+ {
+ var solution = Workspace.CurrentSolution;
+ var projects = solution.GetProjectsForPath(FileName);
+ var analyzerReferences = projects.SelectMany(p => p.AnalyzerReferences).DistinctBy(a => a.Id).ToImmutableArray();
+ foreach (var analyzerReference in analyzerReferences)
+ {
+ var configSettings = GetSettings(analyzerReference, editorConfigOptions);
+ AddRange(configSettings);
+ }
+ }
+
+ private IEnumerable GetSettings(AnalyzerReference analyzerReference, AnalyzerConfigOptions editorConfigOptions)
+ {
+ IEnumerable csharpAnalyzers = analyzerReference.GetAnalyzers(LanguageNames.CSharp);
+ IEnumerable visualBasicAnalyzers = analyzerReference.GetAnalyzers(LanguageNames.VisualBasic);
+ var dotnetAnalyzers = csharpAnalyzers.Intersect(visualBasicAnalyzers, DiagnosticAnalyzerComparer.Instance);
+ csharpAnalyzers = csharpAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance);
+ visualBasicAnalyzers = visualBasicAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance);
+
+ var csharpSettings = ToAnalyzerSetting(csharpAnalyzers, Language.CSharp);
+ var csharpAndVisualBasicSettings = csharpSettings.Concat(ToAnalyzerSetting(visualBasicAnalyzers, Language.VisualBasic));
+ return csharpAndVisualBasicSettings.Concat(ToAnalyzerSetting(dotnetAnalyzers, Language.CSharp | Language.VisualBasic));
+
+ IEnumerable ToAnalyzerSetting(IEnumerable analyzers,
+ Language language)
+ {
+ return analyzers
+ .SelectMany(a => _analyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a))
+ .GroupBy(d => d.Id)
+ .OrderBy(g => g.Key, StringComparer.CurrentCulture)
+ .Select(g =>
+ {
+ var selectedDiagnostic = g.First();
+ var severity = selectedDiagnostic.GetEffectiveSeverity(editorConfigOptions);
+ return new AnalyzerSetting(selectedDiagnostic, severity, SettingsUpdater, language);
+ });
+ }
+ }
+
+ private class DiagnosticAnalyzerComparer : IEqualityComparer
+ {
+ public static readonly DiagnosticAnalyzerComparer Instance = new();
+
+ public bool Equals(DiagnosticAnalyzer? x, DiagnosticAnalyzer? y)
+ {
+ if (x is null && y is null)
+ return true;
+
+ if (x is null || y is null)
+ return false;
+
+ return x.GetAnalyzerIdAndVersion().GetHashCode() == y.GetAnalyzerIdAndVersion().GetHashCode();
+ }
+
+ public int GetHashCode(DiagnosticAnalyzer obj) => obj.GetAnalyzerIdAndVersion().GetHashCode();
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs
new file mode 100644
index 0000000000000..9f145c3f021d0
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyzer
+{
+ internal class AnalyzerSettingsProviderFactory : IWorkspaceSettingsProviderFactory
+ {
+ private readonly Workspace _workspace;
+ private readonly IDiagnosticAnalyzerService _analyzerService;
+
+ public AnalyzerSettingsProviderFactory(Workspace workspace, IDiagnosticAnalyzerService analyzerService)
+ {
+ _workspace = workspace;
+ _analyzerService = analyzerService;
+ }
+
+ public ISettingsProvider GetForFile(string filePath)
+ {
+ var updater = new AnalyzerSettingsUpdater(_workspace, filePath);
+ return new AnalyzerSettingsProvider(filePath, updater, _workspace, _analyzerService);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs
new file mode 100644
index 0000000000000..5b7ed248bf9d8
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.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.Composition;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyzer
+{
+ [ExportWorkspaceServiceFactory(typeof(IWorkspaceSettingsProviderFactory)), Shared]
+ internal class AnalyzerSettingsWorkspaceServiceFactory : IWorkspaceServiceFactory
+ {
+ private readonly IDiagnosticAnalyzerService _analyzerService;
+
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public AnalyzerSettingsWorkspaceServiceFactory(IDiagnosticAnalyzerService analyzerService)
+ {
+ _analyzerService = analyzerService;
+ }
+
+ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
+ => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, _analyzerService);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsProvider.cs
new file mode 100644
index 0000000000000..adcba7ae06210
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsProvider.cs
@@ -0,0 +1,190 @@
+// 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.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.CodeStyle
+{
+ internal class CommonCodeStyleSettingsProvider : SettingsProviderBase
+ {
+ public CommonCodeStyleSettingsProvider(string filePath, OptionUpdater settingsUpdater, Workspace workspace)
+ : base(filePath, settingsUpdater, workspace)
+ {
+ Update();
+ }
+
+ protected override void UpdateOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions)
+ {
+ var qualifySettings = GetQualifyCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(qualifySettings);
+
+ var predefinedTypesSettings = GetPredefinedTypesCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(predefinedTypesSettings);
+
+ var nullCheckingSettings = GetNullCheckingCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(nullCheckingSettings);
+
+ var modifierSettings = GetModifierCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(modifierSettings);
+
+ var codeBlockSettings = GetCodeBlockCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(codeBlockSettings);
+
+ var expressionSettings = GetExpressionCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(expressionSettings);
+
+ var parameterSettings = GetParameterCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(parameterSettings);
+
+ var parenthesesSettings = GetParenthesesCodeStyleOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(parenthesesSettings);
+
+ // TODO(jmarolf): set as stable
+ }
+
+ private static IEnumerable GetQualifyCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.QualifyFieldAccess,
+ description: EditorFeaturesResources.Qualify_field_access_with_this_or_Me,
+ trueValueDescription: EditorFeaturesResources.Prefer_this_or_Me,
+ falseValueDescription: EditorFeaturesResources.Do_not_prefer_this_or_Me,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.QualifyPropertyAccess,
+ description: EditorFeaturesResources.Qualify_property_access_with_this_or_Me,
+ trueValueDescription: EditorFeaturesResources.Prefer_this_or_Me,
+ falseValueDescription: EditorFeaturesResources.Do_not_prefer_this_or_Me,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.QualifyMethodAccess,
+ description: EditorFeaturesResources.Qualify_method_access_with_this_or_Me,
+ trueValueDescription: EditorFeaturesResources.Prefer_this_or_Me,
+ falseValueDescription: EditorFeaturesResources.Do_not_prefer_this_or_Me,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.QualifyEventAccess,
+ description: EditorFeaturesResources.Qualify_event_access_with_this_or_Me,
+ trueValueDescription: EditorFeaturesResources.Prefer_this_or_Me,
+ falseValueDescription: EditorFeaturesResources.Do_not_prefer_this_or_Me,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ }
+
+ private static IEnumerable GetPredefinedTypesCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration,
+ description: EditorFeaturesResources.For_locals_parameters_and_members,
+ trueValueDescription: EditorFeaturesResources.Prefer_predefined_type,
+ falseValueDescription: EditorFeaturesResources.Prefer_framework_type,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration,
+ description: EditorFeaturesResources.For_member_access_expressions,
+ trueValueDescription: EditorFeaturesResources.Prefer_predefined_type,
+ falseValueDescription: EditorFeaturesResources.Prefer_framework_type,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ }
+
+ private static IEnumerable GetNullCheckingCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferCoalesceExpression,
+ description: EditorFeaturesResources.Prefer_coalesce_expression,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferNullPropagation,
+ description: EditorFeaturesResources.Prefer_null_propagation,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod,
+ description: EditorFeaturesResources.Prefer_is_null_for_reference_equality_checks,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ }
+
+ private static IEnumerable GetModifierCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferReadonly,
+ description: EditorFeaturesResources.Prefer_readonly_fields,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ }
+
+ private static IEnumerable GetCodeBlockCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferAutoProperties,
+ description: EditorFeaturesResources.analyzer_Prefer_auto_properties,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.PreferSystemHashCode,
+ description: EditorFeaturesResources.Prefer_System_HashCode_in_GetHashCode,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ }
+
+ private static IEnumerable GetExpressionCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferObjectInitializer, description: EditorFeaturesResources.Prefer_object_initializer, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferCollectionInitializer, description: EditorFeaturesResources.Prefer_collection_initializer, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferSimplifiedBooleanExpressions, description: EditorFeaturesResources.Prefer_simplified_boolean_expressions, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferConditionalExpressionOverAssignment, description: EditorFeaturesResources.Prefer_conditional_expression_over_if_with_assignments, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferConditionalExpressionOverReturn, description: EditorFeaturesResources.Prefer_conditional_expression_over_if_with_returns, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferExplicitTupleNames, description: EditorFeaturesResources.Prefer_explicit_tuple_name, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferInferredTupleNames, description: EditorFeaturesResources.Prefer_inferred_tuple_names, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames, description: EditorFeaturesResources.Prefer_inferred_anonymous_type_member_names, options, visualStudioOptions, updater);
+ yield return CodeStyleSetting.Create(CodeStyleOptions2.PreferCompoundAssignment, description: EditorFeaturesResources.Prefer_compound_assignments, options, visualStudioOptions, updater);
+ }
+
+ private static IEnumerable GetParenthesesCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ var enumValues = new[] { ParenthesesPreference.AlwaysForClarity, ParenthesesPreference.NeverIfUnnecessary };
+ var valueDescriptions = new[] { EditorFeaturesResources.Always_for_clarity, EditorFeaturesResources.Never_if_unnecessary };
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.ArithmeticBinaryParentheses,
+ description: EditorFeaturesResources.In_arithmetic_binary_operators,
+ enumValues: enumValues,
+ valueDescriptions: valueDescriptions,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.OtherBinaryParentheses,
+ description: EditorFeaturesResources.In_other_binary_operators,
+ enumValues: enumValues,
+ valueDescriptions: valueDescriptions,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.RelationalBinaryParentheses,
+ description: EditorFeaturesResources.In_relational_binary_operators,
+ enumValues: enumValues,
+ valueDescriptions: valueDescriptions,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+
+ yield return CodeStyleSetting.Create(option: CodeStyleOptions2.OtherParentheses,
+ description: EditorFeaturesResources.In_other_operators,
+ enumValues: enumValues,
+ valueDescriptions: valueDescriptions,
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+
+ }
+
+ private static IEnumerable GetParameterCodeStyleOptions(AnalyzerConfigOptions options, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return CodeStyleSetting.Create(
+ option: CodeStyleOptions2.UnusedParameters,
+ description: EditorFeaturesResources.Avoid_unused_parameters,
+ enumValues: new[] { UnusedParametersPreference.NonPublicMethods, UnusedParametersPreference.AllMethods },
+ new[] { EditorFeaturesResources.Non_public_methods, EditorFeaturesResources.All_methods },
+ editorConfigOptions: options,
+ visualStudioOptions: visualStudioOptions, updater: updater);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsProviderFactory.cs
new file mode 100644
index 0000000000000..2477ecf668c1c
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsProviderFactory.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.CodeStyle
+{
+ internal class CommonCodeStyleSettingsProviderFactory : IWorkspaceSettingsProviderFactory
+ {
+ private readonly Workspace _workspace;
+
+ public CommonCodeStyleSettingsProviderFactory(Workspace workspace) => _workspace = workspace;
+
+ public ISettingsProvider GetForFile(string filePath)
+ => new CommonCodeStyleSettingsProvider(filePath, new OptionUpdater(_workspace, filePath), _workspace);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsWorkspaceServiceFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsWorkspaceServiceFactory.cs
new file mode 100644
index 0000000000000..2a34627f74a2d
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CodeStyle/CommonCodeStyleSettingsWorkspaceServiceFactory.cs
@@ -0,0 +1,23 @@
+// 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.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.CodeStyle
+{
+ [ExportWorkspaceServiceFactory(typeof(IWorkspaceSettingsProviderFactory)), Shared]
+ internal class CommonCodeStyleSettingsWorkspaceServiceFactory : IWorkspaceServiceFactory
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public CommonCodeStyleSettingsWorkspaceServiceFactory() { }
+
+ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
+ => new CommonCodeStyleSettingsProviderFactory(workspaceServices.Workspace);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CombinedOptionsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CombinedOptionsProviderFactory.cs
new file mode 100644
index 0000000000000..1000896c6a68e
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CombinedOptionsProviderFactory.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.Immutable;
+using Microsoft.CodeAnalysis.Shared.Collections;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal class CombinedOptionsProviderFactory : ISettingsProviderFactory
+ {
+ private ImmutableArray> _factories;
+
+ public CombinedOptionsProviderFactory(ImmutableArray> factories)
+ {
+ _factories = factories;
+ }
+
+ public ISettingsProvider GetForFile(string filePath)
+ {
+ var providers = TemporaryArray>.Empty;
+ foreach (var factory in _factories)
+ {
+ providers.Add(factory.GetForFile(filePath));
+ }
+
+ return new CombinedProvider(providers.ToImmutableAndClear());
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CombinedProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CombinedProvider.cs
new file mode 100644
index 0000000000000..f7eef8dddc1e7
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/CombinedProvider.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal class CombinedProvider : ISettingsProvider
+ {
+ private readonly ImmutableArray> _providers;
+
+ public CombinedProvider(ImmutableArray> providers)
+ {
+ _providers = providers;
+ }
+
+ public async Task GetChangedEditorConfigAsync(SourceText sourceText)
+ {
+ foreach (var provider in _providers)
+ {
+ sourceText = await provider.GetChangedEditorConfigAsync(sourceText).ConfigureAwait(false);
+ }
+
+ return sourceText;
+ }
+
+ public ImmutableArray GetCurrentDataSnapshot()
+ {
+ var snapShot = ImmutableArray.Empty;
+ foreach (var provider in _providers)
+ {
+ snapShot = snapShot.Concat(provider.GetCurrentDataSnapshot());
+ }
+
+ return snapShot;
+ }
+
+ public void RegisterViewModel(ISettingsEditorViewModel model)
+ {
+ foreach (var provider in _providers)
+ {
+ provider.RegisterViewModel(model);
+ }
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsProvider.cs
new file mode 100644
index 0000000000000..4a9a86ad21ada
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsProvider.cs
@@ -0,0 +1,38 @@
+// 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.Threading.Tasks;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Formatting
+{
+ internal class CommonFormattingSettingsProvider : SettingsProviderBase
+ {
+ public CommonFormattingSettingsProvider(string fileName, OptionUpdater settingsUpdater, Workspace workspace)
+ : base(fileName, settingsUpdater, workspace)
+ {
+ Update();
+ }
+
+ protected override void UpdateOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions)
+ {
+ var defaultOptions = GetDefaultOptions(editorConfigOptions, visualStudioOptions, SettingsUpdater);
+ AddRange(defaultOptions);
+ }
+
+ private static IEnumerable GetDefaultOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions, OptionUpdater updater)
+ {
+ yield return FormattingSetting.Create(FormattingOptions2.UseTabs, EditorFeaturesResources.Use_Tabs, editorConfigOptions, visualStudioOptions, updater);
+ yield return FormattingSetting.Create(FormattingOptions2.TabSize, EditorFeaturesResources.Tab_Size, editorConfigOptions, visualStudioOptions, updater);
+ yield return FormattingSetting.Create(FormattingOptions2.IndentationSize, EditorFeaturesResources.Indentation_Size, editorConfigOptions, visualStudioOptions, updater);
+ yield return FormattingSetting.Create(FormattingOptions2.NewLine, EditorFeaturesResources.New_Line, editorConfigOptions, visualStudioOptions, updater);
+ yield return FormattingSetting.Create(FormattingOptions2.InsertFinalNewLine, EditorFeaturesResources.Insert_Final_Newline, editorConfigOptions, visualStudioOptions, updater);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsProviderFactory.cs
new file mode 100644
index 0000000000000..21d130c599f42
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsProviderFactory.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Formatting
+{
+ internal class CommonFormattingSettingsProviderFactory : IWorkspaceSettingsProviderFactory
+ {
+ private readonly Workspace _workspace;
+
+ public CommonFormattingSettingsProviderFactory(Workspace workspace) => _workspace = workspace;
+
+ public ISettingsProvider GetForFile(string filePath)
+ => new CommonFormattingSettingsProvider(filePath, new OptionUpdater(_workspace, filePath), _workspace);
+
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsWorkspaceServiceFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsWorkspaceServiceFactory.cs
new file mode 100644
index 0000000000000..878bd1198987d
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Formatting/CommonFormattingSettingsWorkspaceServiceFactory.cs
@@ -0,0 +1,23 @@
+// 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.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Formatting
+{
+ [ExportWorkspaceServiceFactory(typeof(IWorkspaceSettingsProviderFactory)), Shared]
+ internal class CommonFormattingSettingsWorkspaceServiceFactory : IWorkspaceServiceFactory
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public CommonFormattingSettingsWorkspaceServiceFactory() { }
+
+ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
+ => new CommonFormattingSettingsProviderFactory(workspaceServices.Workspace);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ILanguageSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ILanguageSettingsProviderFactory.cs
new file mode 100644
index 0000000000000..800df15d4f056
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ILanguageSettingsProviderFactory.cs
@@ -0,0 +1,12 @@
+// 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.Host;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal interface ILanguageSettingsProviderFactory : ISettingsProviderFactory, ILanguageService
+ {
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ISettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ISettingsProvider.cs
new file mode 100644
index 0000000000000..7eaece9b9f9ed
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ISettingsProvider.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal interface ISettingsProvider
+ {
+ void RegisterViewModel(ISettingsEditorViewModel model);
+ ImmutableArray GetCurrentDataSnapshot();
+ Task GetChangedEditorConfigAsync(SourceText sourceText);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ISettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ISettingsProviderFactory.cs
new file mode 100644
index 0000000000000..4c87bfb021ae3
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/ISettingsProviderFactory.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal interface ISettingsProviderFactory
+ {
+ ISettingsProvider GetForFile(string filePath);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/IWorkspaceSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/IWorkspaceSettingsProviderFactory.cs
new file mode 100644
index 0000000000000..a241256ac0d26
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/IWorkspaceSettingsProviderFactory.cs
@@ -0,0 +1,12 @@
+// 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.Host;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal interface IWorkspaceSettingsProviderFactory : ISettingsProviderFactory, IWorkspaceService
+ {
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs
new file mode 100644
index 0000000000000..c39d19813506b
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs
@@ -0,0 +1,130 @@
+// 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.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.Utilities;
+using static Microsoft.CodeAnalysis.ProjectState;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
+{
+ internal abstract class SettingsProviderBase : ISettingsProvider
+ where TOptionsUpdater : ISettingUpdater
+ {
+ private readonly List _snapshot = new();
+ private static readonly object s_gate = new();
+ private ISettingsEditorViewModel? _viewModel;
+ protected readonly string FileName;
+ protected readonly TOptionsUpdater SettingsUpdater;
+ protected readonly Workspace Workspace;
+
+ protected abstract void UpdateOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions);
+
+ protected SettingsProviderBase(string fileName, TOptionsUpdater settingsUpdater, Workspace workspace)
+ {
+ FileName = fileName;
+ SettingsUpdater = settingsUpdater;
+ Workspace = workspace;
+ }
+
+ protected void Update()
+ {
+ var givenFolder = new DirectoryInfo(FileName).Parent;
+ var solution = Workspace.CurrentSolution;
+ var projects = solution.GetProjectsForPath(FileName);
+ var project = projects.First();
+ var configOptionsProvider = new WorkspaceAnalyzerConfigOptionsProvider(project.State);
+ var workspaceOptions = configOptionsProvider.GetOptionsForSourcePath(givenFolder.FullName);
+ var result = project.GetAnalyzerConfigOptions();
+ var options = new CombinedAnalyzerConfigOptions(workspaceOptions, result);
+ UpdateOptions(options, Workspace.Options);
+ }
+
+ public async Task GetChangedEditorConfigAsync(SourceText sourceText)
+ {
+ if (!await SettingsUpdater.HasAnyChangesAsync().ConfigureAwait(false))
+ {
+ return sourceText;
+ }
+
+ var text = await SettingsUpdater.GetChangedEditorConfigAsync(sourceText, default).ConfigureAwait(false);
+ return text is not null ? text : sourceText;
+ }
+
+ public ImmutableArray GetCurrentDataSnapshot()
+ {
+ lock (s_gate)
+ {
+ return _snapshot.ToImmutableArray();
+ }
+ }
+
+ protected void AddRange(IEnumerable items)
+ {
+ lock (s_gate)
+ {
+ _snapshot.AddRange(items);
+ }
+
+ _viewModel?.NotifyOfUpdate();
+ }
+
+ public void RegisterViewModel(ISettingsEditorViewModel viewModel)
+ => _viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
+
+ private sealed class CombinedAnalyzerConfigOptions : AnalyzerConfigOptions
+ {
+ private readonly AnalyzerConfigOptions _workspaceOptions;
+ private readonly AnalyzerConfigOptionsResult? _result;
+
+ public CombinedAnalyzerConfigOptions(AnalyzerConfigOptions workspaceOptions, AnalyzerConfigOptionsResult? result)
+ {
+ _workspaceOptions = workspaceOptions;
+ _result = result;
+ }
+
+ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
+ {
+ if (_workspaceOptions.TryGetValue(key, out value))
+ {
+ return true;
+ }
+
+ if (!_result.HasValue)
+ {
+ value = null;
+ return false;
+ }
+
+ if (_result.Value.AnalyzerOptions.TryGetValue(key, out value))
+ {
+ return true;
+ }
+
+ var diagnosticKey = "dotnet_diagnostic.(?.*).severity";
+ var match = Regex.Match(key, diagnosticKey);
+ if (match.Success && match.Groups["key"].Value is string isolatedKey &&
+ _result.Value.TreeOptions.TryGetValue(isolatedKey, out var severity))
+ {
+ value = severity.ToEditorConfigString();
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Extensions/EnumerableExtensions.cs b/src/EditorFeatures/Core/EditorConfigSettings/Extensions/EnumerableExtensions.cs
new file mode 100644
index 0000000000000..6ef0a5228cda0
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Extensions/EnumerableExtensions.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions
+{
+ internal static class EnumerableExtensions
+ {
+ public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector)
+ {
+ var seenKeys = new HashSet();
+ foreach (var element in source)
+ {
+ if (seenKeys.Add(keySelector(element)))
+ {
+ yield return element;
+ }
+ }
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs b/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs
new file mode 100644
index 0000000000000..fe06a29a6b5c8
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Extensions/SolutionExtensions.cs
@@ -0,0 +1,59 @@
+// 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.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis.PooledObjects;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions
+{
+ internal static class SolutionExtensions
+ {
+ public static ImmutableArray GetProjectsForPath(this Solution solution, string givenPath)
+ {
+ if (Path.GetDirectoryName(givenPath) is not string givenFolderPath ||
+ solution.FilePath is null)
+ {
+ return solution.Projects.ToImmutableArray();
+ }
+
+ var givenFolder = new DirectoryInfo(givenFolderPath);
+ if (givenFolder.FullName == (new DirectoryInfo(solution.FilePath).Parent).FullName)
+ {
+ return solution.Projects.ToImmutableArray();
+ }
+
+ var builder = ArrayBuilder.GetInstance();
+ foreach (var (projectDirectoryPath, project) in solution.Projects.Select(p => (new DirectoryInfo(p.FilePath).Parent, p)))
+ {
+ if (ContainsPath(givenFolder, projectDirectoryPath))
+ {
+ builder.Add(project);
+ }
+ }
+
+ return builder.ToImmutableAndFree();
+
+ static bool ContainsPath(DirectoryInfo givenPath, DirectoryInfo projectPath)
+ {
+ if (projectPath.FullName == givenPath.FullName)
+ {
+ return true;
+ }
+
+ while (projectPath.Parent is not null)
+ {
+ if (projectPath.Parent.FullName == givenPath.FullName)
+ {
+ return true;
+ }
+ projectPath = projectPath.Parent;
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/ISettingsEditorViewModel.cs b/src/EditorFeatures/Core/EditorConfigSettings/ISettingsEditorViewModel.cs
new file mode 100644
index 0000000000000..5cabb84670fd5
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/ISettingsEditorViewModel.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.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Editor
+{
+ internal interface ISettingsEditorViewModel
+ {
+ void NotifyOfUpdate();
+ Task UpdateEditorConfigAsync(SourceText sourceText);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Language.cs b/src/EditorFeatures/Core/EditorConfigSettings/Language.cs
new file mode 100644
index 0000000000000..54002e7c301c0
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Language.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;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings
+{
+ [Flags]
+ internal enum Language
+ {
+ CSharp = 1,
+ VisualBasic = 2,
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/AnalyzerSettingsUpdater.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/AnalyzerSettingsUpdater.cs
new file mode 100644
index 0000000000000..f96b83172e91a
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/AnalyzerSettingsUpdater.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater
+{
+ internal class AnalyzerSettingsUpdater : SettingsUpdaterBase
+ {
+ public AnalyzerSettingsUpdater(Workspace workspace, string editorconfigPath) : base(workspace, editorconfigPath)
+ {
+ }
+
+ protected override SourceText? GetNewText(SourceText sourceText,
+ IReadOnlyList<(AnalyzerSetting option, DiagnosticSeverity value)> settingsToUpdate,
+ CancellationToken token)
+ => SettingsUpdateHelper.TryUpdateAnalyzerConfigDocument(sourceText, EditorconfigPath, settingsToUpdate);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs
new file mode 100644
index 0000000000000..fac1c922c97b6
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater
+{
+ internal interface ISettingUpdater
+ {
+ Task QueueUpdateAsync(TSetting setting, TValue value);
+ Task GetChangedEditorConfigAsync(SourceText sourceText, CancellationToken token);
+ Task HasAnyChangesAsync();
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/OptionUpdater.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/OptionUpdater.cs
new file mode 100644
index 0000000000000..f57e0d103d397
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/OptionUpdater.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.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater
+{
+
+ internal class OptionUpdater : SettingsUpdaterBase
+ {
+ public OptionUpdater(Workspace workspace, string editorconfigPath)
+ : base(workspace, editorconfigPath)
+ {
+ }
+
+ protected override SourceText? GetNewText(SourceText SourceText,
+ IReadOnlyList<(IOption2 option, object value)> settingsToUpdate,
+ CancellationToken token)
+ => SettingsUpdateHelper.TryUpdateAnalyzerConfigDocument(SourceText, EditorconfigPath, Workspace.Options, settingsToUpdate);
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs
new file mode 100644
index 0000000000000..dd5ca9a124dc7
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs
@@ -0,0 +1,352 @@
+// 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.RegularExpressions;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater
+{
+ internal static partial class SettingsUpdateHelper
+ {
+ private const string DiagnosticOptionPrefix = "dotnet_diagnostic.";
+ private const string SeveritySuffix = ".severity";
+
+ public static SourceText? TryUpdateAnalyzerConfigDocument(SourceText originalText,
+ string filePath,
+ IReadOnlyList<(AnalyzerSetting option, DiagnosticSeverity value)> settingsToUpdate)
+ {
+ if (originalText is null)
+ return null;
+ if (settingsToUpdate is null)
+ return null;
+ if (filePath is null)
+ return null;
+
+ var settings = settingsToUpdate.Select(x => TryGetOptionValueAndLanguage(x.option, x.value)).ToList();
+
+ return TryUpdateAnalyzerConfigDocument(originalText, filePath, settings);
+
+ static (string option, string value, Language language) TryGetOptionValueAndLanguage(AnalyzerSetting diagnostic, DiagnosticSeverity severity)
+ {
+ var optionName = $"{DiagnosticOptionPrefix}{diagnostic.Id}{SeveritySuffix}";
+ var optionValue = severity.ToEditorConfigString();
+ var language = diagnostic.Language;
+ return (optionName, optionValue, language);
+ }
+ }
+
+ public static SourceText? TryUpdateAnalyzerConfigDocument(SourceText originalText,
+ string filePath,
+ OptionSet optionSet,
+ IReadOnlyList<(IOption2 option, object value)> settingsToUpdate)
+ {
+ if (originalText is null)
+ return null;
+ if (settingsToUpdate is null)
+ return null;
+ if (filePath is null)
+ return null;
+
+ var updatedText = originalText;
+ var settings = settingsToUpdate.Select(x => TryGetOptionValueAndLanguage(x.option, x.value, optionSet))
+ .Where(x => x.success)
+ .Select(x => (x.option, x.value, x.language))
+ .ToList();
+
+ return TryUpdateAnalyzerConfigDocument(originalText, filePath, settings);
+
+ static (bool success, string option, string value, Language language) TryGetOptionValueAndLanguage(IOption2 option, object value, OptionSet optionSet)
+ {
+ if (option.StorageLocations.FirstOrDefault(x => x is IEditorConfigStorageLocation2) is not IEditorConfigStorageLocation2 storageLocation)
+ {
+ return (false, null!, null!, default);
+ }
+
+ var optionName = storageLocation.KeyName;
+ var optionValue = storageLocation.GetEditorConfigStringValue(value, optionSet);
+ if (value is ICodeStyleOption codeStyleOption && !optionValue.Contains(':'))
+ {
+ var severity = codeStyleOption.Notification switch
+ {
+ { Severity: ReportDiagnostic.Hidden } => "silent",
+ { Severity: ReportDiagnostic.Info } => "suggestion",
+ { Severity: ReportDiagnostic.Warn } => "warning",
+ { Severity: ReportDiagnostic.Error } => "error",
+ _ => string.Empty
+ };
+ optionValue = $"{optionValue}:{severity}";
+ }
+
+ var language = option.IsPerLanguage ? Language.CSharp | Language.VisualBasic : Language.CSharp;
+ return (true, optionName, optionValue, language);
+ }
+ }
+
+ public static SourceText? TryUpdateAnalyzerConfigDocument(SourceText originalText,
+ string filePath,
+ IReadOnlyList<(string option, string value, Language language)> settingsToUpdate)
+ {
+ if (originalText is null)
+ throw new ArgumentNullException(nameof(originalText));
+ if (filePath is null)
+ throw new ArgumentNullException(nameof(filePath));
+ if (settingsToUpdate is null)
+ throw new ArgumentNullException(nameof(settingsToUpdate));
+
+ var updatedText = originalText;
+ TextLine? lastValidHeaderSpanEnd;
+ TextLine? lastValidSpecificHeaderSpanEnd;
+ foreach (var (option, value, language) in settingsToUpdate)
+ {
+ SourceText? newText;
+ (newText, lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd) = UpdateIfExistsInFile(updatedText, filePath, option, value, language);
+ if (newText != null)
+ {
+ updatedText = newText;
+ continue;
+ }
+
+ (newText, lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd) = AddMissingRule(updatedText, lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd, option, value, language);
+ if (newText != null)
+ {
+ updatedText = newText;
+ }
+ }
+
+ return updatedText.Equals(originalText) ? null : updatedText;
+ }
+
+ ///
+ /// Regular expression for .editorconfig header.
+ /// For example: "[*.cs] # Optional comment"
+ /// "[*.{vb,cs}]"
+ /// "[*] ; Optional comment"
+ /// "[ConsoleApp/Program.cs]"
+ ///
+ private static readonly Regex s_headerPattern = new(@"\[(\*|[^ #;\[\]]+\.({[^ #;{}\.\[\]]+}|[^ #;{}\.\[\]]+))\]\s*([#;].*)?");
+
+ ///
+ /// Regular expression for .editorconfig code style option entry.
+ /// For example:
+ /// 1. "dotnet_style_object_initializer = true # Optional comment"
+ /// 2. "dotnet_style_object_initializer = true:suggestion ; Optional comment"
+ /// 3. "dotnet_diagnostic.CA2000.severity = suggestion # Optional comment"
+ /// 4. "dotnet_analyzer_diagnostic.category-Security.severity = suggestion # Optional comment"
+ /// 5. "dotnet_analyzer_diagnostic.severity = suggestion # Optional comment"
+ /// Regex groups:
+ /// 1. Option key
+ /// 2. Option value
+ /// 3. Optional severity suffix in option value, i.e. ':severity' suffix
+ /// 4. Optional comment suffix
+ ///
+ private static readonly Regex s_optionEntryPattern = new($@"(.*)=([\w, ]*)(:[\w]+)?([ ]*[;#].*)?");
+
+ private static (SourceText? newText, TextLine? lastValidHeaderSpanEnd, TextLine? lastValidSpecificHeaderSpanEnd) UpdateIfExistsInFile(SourceText editorConfigText,
+ string filePath,
+ string optionName,
+ string optionValue,
+ Language language)
+ {
+ var editorConfigDirectory = PathUtilities.GetDirectoryName(filePath);
+ Assumes.NotNull(editorConfigDirectory);
+ var relativePath = PathUtilities.GetRelativePath(editorConfigDirectory.ToLowerInvariant(), filePath);
+
+ TextLine? mostRecentHeader = null;
+ TextLine? lastValidHeader = null;
+ TextLine? lastValidHeaderSpanEnd = null;
+
+ TextLine? lastValidSpecificHeader = null;
+ TextLine? lastValidSpecificHeaderSpanEnd = null;
+
+ var textChange = new TextChange();
+ foreach (var curLine in editorConfigText.Lines)
+ {
+ var curLineText = curLine.ToString();
+ if (s_optionEntryPattern.IsMatch(curLineText))
+ {
+ var groups = s_optionEntryPattern.Match(curLineText).Groups;
+ var (untrimmedKey, key, value, severity, comment) = GetGroups(groups);
+
+ // Verify the most recent header is a valid header
+ if (IsValidHeader(mostRecentHeader, lastValidHeader) &&
+ string.Equals(key, optionName, StringComparison.OrdinalIgnoreCase))
+ {
+ // We found the rule in the file -- replace it with updated option value.
+ textChange = new TextChange(curLine.Span, $"{untrimmedKey}={optionValue}{comment}");
+ }
+ }
+ else if (s_headerPattern.IsMatch(curLineText.Trim()))
+ {
+ mostRecentHeader = curLine;
+ if (ShouldSetAsLastValidHeader(curLineText, out var mostRecentHeaderText))
+ {
+ lastValidHeader = mostRecentHeader;
+ }
+ else
+ {
+ var (fileName, splicedFileExtensions) = ParseHeaderParts(mostRecentHeaderText);
+ if ((relativePath.IsEmpty() || new Regex(fileName).IsMatch(relativePath)) &&
+ HeaderMatchesLanguageRequirements(language, splicedFileExtensions))
+ {
+ lastValidHeader = mostRecentHeader;
+ }
+ }
+ }
+
+ // We want to keep track of how far this (valid) section spans.
+ if (IsValidHeader(mostRecentHeader, lastValidHeader) && IsNotEmptyOrComment(curLineText))
+ {
+ lastValidHeaderSpanEnd = curLine;
+ if (lastValidSpecificHeader != null && mostRecentHeader.Equals(lastValidSpecificHeader))
+ {
+ lastValidSpecificHeaderSpanEnd = curLine;
+ }
+ }
+ }
+
+ // We return only the last text change in case of duplicate entries for the same rule.
+ if (textChange != default)
+ {
+ return (editorConfigText.WithChanges(textChange), lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd);
+ }
+
+ // Rule not found.
+ return (null, lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd);
+
+ static (string untrimmedKey, string key, string value, string severitySuffixInValue, string commentValue) GetGroups(GroupCollection groups)
+ {
+ var untrimmedKey = groups[1].Value.ToString();
+ var key = untrimmedKey.Trim();
+ var value = groups[2].Value.ToString();
+ var severitySuffixInValue = groups[3].Value.ToString();
+ var commentValue = groups[4].Value.ToString();
+ return (untrimmedKey, key, value, severitySuffixInValue, commentValue);
+ }
+
+ static bool IsValidHeader(TextLine? mostRecentHeader, TextLine? lastValidHeader)
+ {
+ return mostRecentHeader is not null &&
+ lastValidHeader is not null &&
+ mostRecentHeader.Equals(lastValidHeader);
+ }
+
+ static bool ShouldSetAsLastValidHeader(string curLineText, out string mostRecentHeaderText)
+ {
+ var groups = s_headerPattern.Match(curLineText.Trim()).Groups;
+ mostRecentHeaderText = groups[1].Value.ToString().ToLowerInvariant();
+ return mostRecentHeaderText.Equals("*", StringComparison.Ordinal);
+ }
+
+ static (string fileName, string[] splicedFileExtensions) ParseHeaderParts(string mostRecentHeaderText)
+ {
+ // We splice on the last occurrence of '.' to account for filenames containing periods.
+ var nameExtensionSplitIndex = mostRecentHeaderText.LastIndexOf('.');
+ var fileName = mostRecentHeaderText.Substring(0, nameExtensionSplitIndex);
+ var splicedFileExtensions = mostRecentHeaderText[(nameExtensionSplitIndex + 1)..].Split(',', ' ', '{', '}');
+
+ // Replacing characters in the header with the regex equivalent.
+ fileName = fileName.Replace(".", @"\.");
+ fileName = fileName.Replace("*", ".*");
+ fileName = fileName.Replace("/", @"\/");
+
+ return (fileName, splicedFileExtensions);
+ }
+
+ static bool IsNotEmptyOrComment(string currentLineText)
+ {
+ return !string.IsNullOrWhiteSpace(currentLineText) && !currentLineText.Trim().StartsWith("#", StringComparison.OrdinalIgnoreCase);
+ }
+
+ static bool HeaderMatchesLanguageRequirements(Language language, string[] splicedFileExtensions)
+ {
+ return IsCSharpOnly(language, splicedFileExtensions) || IsVisualBasicOnly(language, splicedFileExtensions) || IsBothVisualBasicAndCSharp(language, splicedFileExtensions);
+ }
+
+ static bool IsCSharpOnly(Language language, string[] splicedFileExtensions)
+ {
+ return language.HasFlag(Language.CSharp) && !language.HasFlag(Language.VisualBasic) && splicedFileExtensions.Contains("cs") && splicedFileExtensions.Length == 1;
+ }
+
+ static bool IsVisualBasicOnly(Language language, string[] splicedFileExtensions)
+ {
+ return language.HasFlag(Language.VisualBasic) && !language.HasFlag(Language.CSharp) && splicedFileExtensions.Contains("vb") && splicedFileExtensions.Length == 1;
+ }
+
+ static bool IsBothVisualBasicAndCSharp(Language language, string[] splicedFileExtensions)
+ {
+ return language.HasFlag(Language.VisualBasic) && language.HasFlag(Language.CSharp) && splicedFileExtensions.Contains("vb") && splicedFileExtensions.Contains("cs");
+ }
+ }
+
+ private static (SourceText? newText, TextLine? lastValidHeaderSpanEnd, TextLine? lastValidSpecificHeaderSpanEnd) AddMissingRule(SourceText editorConfigText,
+ TextLine? lastValidHeaderSpanEnd,
+ TextLine? lastValidSpecificHeaderSpanEnd,
+ string optionName,
+ string optionValue,
+ Language language)
+ {
+ var newEntry = $"{optionName}={optionValue}";
+ if (lastValidSpecificHeaderSpanEnd.HasValue)
+ {
+ if (lastValidSpecificHeaderSpanEnd.Value.ToString().Trim().Length != 0)
+ {
+ newEntry = "\r\n" + newEntry; // TODO(jmarolf): do we need to read in the users newline settings?
+ }
+
+ return (editorConfigText.WithChanges((TextChange)new TextChange(new TextSpan(lastValidSpecificHeaderSpanEnd.Value.Span.End, 0), newEntry)), lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd);
+ }
+ else if (lastValidHeaderSpanEnd.HasValue)
+ {
+ if (lastValidHeaderSpanEnd.Value.ToString().Trim().Length != 0)
+ {
+ newEntry = "\r\n" + newEntry; // TODO(jmarolf): do we need to read in the users newline settings?
+ }
+
+ return (editorConfigText.WithChanges((TextChange)new TextChange(new TextSpan(lastValidHeaderSpanEnd.Value.Span.End, 0), newEntry)), lastValidHeaderSpanEnd, lastValidSpecificHeaderSpanEnd);
+ }
+
+ // We need to generate a new header such as '[*.cs]' or '[*.vb]':
+ // - For compiler diagnostic entries and code style entries which have per-language option = false, generate only [*.cs] or [*.vb].
+ // - For the remainder, generate [*.{cs,vb}]
+ // Insert a newline if not already present
+ var lines = editorConfigText.Lines;
+ var lastLine = lines.Count > 0 ? lines[^1] : default;
+ var prefix = string.Empty;
+ if (lastLine.ToString().Trim().Length != 0)
+ {
+ prefix = "\r\n";
+ }
+
+ // Insert newline if file is not empty
+ if (lines.Count > 1 && lastLine.ToString().Trim().Length == 0)
+ {
+ prefix += "\r\n";
+ }
+
+ if (language.HasFlag(Language.CSharp) && language.HasFlag(Language.VisualBasic))
+ {
+ prefix += "[*.{cs,vb}]\r\n";
+ }
+ else if (language.HasFlag(Language.CSharp))
+ {
+ prefix += "[*.cs]\r\n";
+ }
+ else if (language.HasFlag(Language.VisualBasic))
+ {
+ prefix += "[*.vb]\r\n";
+ }
+
+ var result = editorConfigText.WithChanges((TextChange)new TextChange(new TextSpan(editorConfigText.Length, 0), prefix + newEntry));
+ return (result, lastValidHeaderSpanEnd, result.Lines[^2]);
+ }
+ }
+}
diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs
new file mode 100644
index 0000000000000..e9eea754fdfcc
--- /dev/null
+++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs
@@ -0,0 +1,103 @@
+// 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 System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater
+{
+ internal abstract class SettingsUpdaterBase : ISettingUpdater
+ {
+ private readonly List<(TOption option, TValue value)> _queue = new();
+ private readonly SemaphoreSlim _guard = new(1);
+ protected readonly Workspace Workspace;
+ protected readonly string EditorconfigPath;
+
+ protected abstract SourceText? GetNewText(SourceText analyzerConfigDocument, IReadOnlyList<(TOption option, TValue value)> settingsToUpdate, CancellationToken token);
+
+ protected SettingsUpdaterBase(Workspace workspace, string editorconfigPath)
+ {
+ Workspace = workspace;
+ EditorconfigPath = editorconfigPath;
+ }
+
+ public async Task QueueUpdateAsync(TOption setting, TValue value)
+ {
+ using (await _guard.DisposableWaitAsync().ConfigureAwait(false))
+ {
+ _queue.Add((setting, value));
+ }
+
+ return true;
+ }
+
+ public async Task GetChangedEditorConfigAsync(AnalyzerConfigDocument analyzerConfigDocument, CancellationToken token)
+ {
+ if (analyzerConfigDocument is null)
+ return null;
+
+ var originalText = await analyzerConfigDocument.GetTextAsync(token).ConfigureAwait(false);
+ using (await _guard.DisposableWaitAsync(token).ConfigureAwait(false))
+ {
+ var newText = GetNewText(originalText, _queue, token);
+ if (newText is null || newText.Equals(originalText))
+ {
+ _queue.Clear();
+ return null;
+ }
+ else
+ {
+ _queue.Clear();
+ return newText;
+ }
+ }
+ }
+
+ public async Task