diff --git a/build/Targets/GenerateCompilerExecutableBindingRedirects.targets b/build/Targets/GenerateCompilerExecutableBindingRedirects.targets
index 049ab8e118f9d..9141f1ddc3996 100644
--- a/build/Targets/GenerateCompilerExecutableBindingRedirects.targets
+++ b/build/Targets/GenerateCompilerExecutableBindingRedirects.targets
@@ -25,7 +25,7 @@
$(AssemblyVersion)
- 1.2.1.0
+ 1.2.3.0
4.0.1.0
@@ -49,7 +49,7 @@
4.0.1.0
- 1.4.1.0
+ 1.4.3.0
4.0.1.0
diff --git a/build/Targets/Packages.props b/build/Targets/Packages.props
index 3d69dbfcd5dec..b61dfe6f12172 100644
--- a/build/Targets/Packages.props
+++ b/build/Targets/Packages.props
@@ -40,18 +40,18 @@
4.3.0
0.8.31-beta
1.0.35
- 1.2.0
- 1.1.0-beta1-62628-01
- 1.1.0-beta1-62628-01
+ 1.3.0-beta-62801-02
+ 1.1.0-beta1-62801-01
+ 1.1.0-beta1-62801-01
1.7.0
- 1.4.0
+ 1.5.0-beta-62801-02
4.7.2-alpha-00001
1.0.27-prerelease-01811-02
2.1.0-prerelease-02419-02
3.13.8
15.0.26730-alpha
14.3.25407-alpha
- 1.0.0-beta1-61531-03
+ 1.0.0-beta1-62801-01
8.0.0.0-alpha
15.6.0-dev
@@ -158,7 +158,7 @@
4.3.0
4.3.0
4.3.0
- 1.3.1
+ 1.5.0-preview2-26401-03
4.3.0
4.3.0
4.3.0
@@ -191,7 +191,7 @@
4.3.0
4.3.0
4.3.0
- 1.4.2
+ 1.6.0-preview2-26401-03
4.3.0
4.3.0
4.3.0
diff --git a/build/Targets/Roslyn.Toolsets.Xunit.targets b/build/Targets/Roslyn.Toolsets.Xunit.targets
index 16fdab15295ee..d8c45d9ea7e43 100644
--- a/build/Targets/Roslyn.Toolsets.Xunit.targets
+++ b/build/Targets/Roslyn.Toolsets.Xunit.targets
@@ -20,7 +20,7 @@
true
true
- $(RoslynDesktopRuntimeIdentifier)
+ $(RoslynDesktopRuntimeIdentifier)
diff --git a/build/Targets/Settings.props b/build/Targets/Settings.props
index c4f11d084b8af..877b9d3d19c60 100644
--- a/build/Targets/Settings.props
+++ b/build/Targets/Settings.props
@@ -37,8 +37,7 @@
False
$(NuGetPackageRoot)/microsoft.net.roslyndiagnostics/$(RoslynDiagnosticsNugetPackageVersion)/build/Microsoft.Net.RoslynDiagnostics.props
- net461;netcoreapp2.0
- net46;netcoreapp2.0
+ net46;netcoreapp2.0
win;win-x64;linux-x64;osx-x64
win
win-x86
diff --git a/build/ToolsetPackages/RoslynToolset.csproj b/build/ToolsetPackages/RoslynToolset.csproj
index 14c542989b43c..cadfc16ce6ba9 100644
--- a/build/ToolsetPackages/RoslynToolset.csproj
+++ b/build/ToolsetPackages/RoslynToolset.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/build/config/SignToolData.json b/build/config/SignToolData.json
index e4ea966a1a817..f21526e2fdd12 100644
--- a/build/config/SignToolData.json
+++ b/build/config/SignToolData.json
@@ -106,7 +106,7 @@
"strongName": null,
"values": [
"Dlls\\ServicesTestUtilities\\Roslyn.Services.Test.Utilities.dll",
- "Dlls\\TestUtilities\\net461\\Roslyn.Test.Utilities.dll",
+ "Dlls\\TestUtilities\\net46\\Roslyn.Test.Utilities.dll",
"Dlls\\VisualStudioIntegrationTestUtilities\\Microsoft.VisualStudio.IntegrationTest.Utilities.dll",
"Vsix\\VisualStudioIntegrationTestSetup\\Microsoft.VisualStudio.IntegrationTest.Setup.dll"
]
@@ -144,7 +144,9 @@
"Vsix\\VisualStudioSetup.Next\\Roslyn.VisualStudio.Setup.Next.vsix",
"Vsix\\VisualStudioSetup\\Roslyn.VisualStudio.Setup.vsix",
"Vsix\\Roslyn\\RoslynDeployment.vsix",
- "Vsix\\VisualStudioIntegrationTestSetup\\Microsoft.VisualStudio.IntegrationTest.Setup.vsix"
+ "Vsix\\VisualStudioIntegrationTestSetup\\Microsoft.VisualStudio.IntegrationTest.Setup.vsix",
+ "Vsix\\CodeAnalysisCompilers\\Microsoft.CodeAnalysis.Compilers.vsix",
+ "Vsix\\PortableFacades\\PortableFacades.vsix"
]
},
{
@@ -224,14 +226,19 @@
"System.IO.Pipes.AccessControl.dll",
"System.IO.Pipes.dll",
"System.IO.dll",
+ "System.Net.Security.dll",
+ "System.Net.Sockets.dll",
"System.Reflection.Metadata.1.0.21.nupkg",
"System.Reflection.Metadata.dll",
+ "System.Reflection.TypeExtensions.dll",
"System.Reflection.dll",
"System.Resources.Reader.dll",
"System.Runtime.InteropServices.RuntimeInformation.dll",
+ "System.Runtime.Serialization.Primitives.dll",
"System.Security.AccessControl.dll",
"System.Security.Claims.dll",
"System.Security.Cryptography.Algorithms.dll",
+ "System.Security.Cryptography.Csp.dll",
"System.Security.Cryptography.Encoding.dll",
"System.Security.Cryptography.Primitives.dll",
"System.Security.Cryptography.X509Certificates.dll",
diff --git a/build/scripts/build-utils.ps1 b/build/scripts/build-utils.ps1
index e7e82c7d46cca..e546b0fa90ffc 100644
--- a/build/scripts/build-utils.ps1
+++ b/build/scripts/build-utils.ps1
@@ -136,7 +136,6 @@ function Ensure-NuGet() {
# Ensure the proper SDK in installed in our %PATH%. This is how MSBuild locates the
# SDK. Returns the location to the dotnet exe
function Ensure-DotnetSdk() {
-
# Check to see if the specified dotnet installations meets our build requirements
function Test-DotnetDir([string]$dotnetDir, [string]$runtimeVersion, [string]$sdkVersion) {
$sdkPath = Join-Path $dotnetDir "sdk\$sdkVersion"
@@ -180,6 +179,9 @@ function Ensure-DotnetSdk() {
Exec-Block { & $destFile -Version $sdkVersion -InstallDir $cliDir } | Out-Null
Exec-Block { & $destFile -Version $runtimeVersion -SharedRuntime -InstallDir $cliDir } | Out-Null
}
+ else {
+ ${env:PATH} = "$cliDir;${env:PATH}"
+ }
return (Join-Path $cliDir "dotnet.exe")
}
diff --git a/build/scripts/build.ps1 b/build/scripts/build.ps1
index da5945bfe47f9..d3d7090fcdb87 100644
--- a/build/scripts/build.ps1
+++ b/build/scripts/build.ps1
@@ -180,7 +180,10 @@ function Restore-Packages() {
Write-Host "Restoring $($both[0])"
$projectFilePath = $both[1]
$projectFileName = [IO.Path]::GetFileNameWithoutExtension($projectFilePath)
- $logFilePath = Join-Path $logsDir "Restore-$($projectFileName).binlog"
+ $logFilePath = ""
+ if ($binaryLog) {
+ $logFilePath = Join-Path $logsDir "Restore-$($projectFileName).binlog"
+ }
Restore-Project $dotnet $both[1] $logFilePath
}
}
@@ -283,6 +286,10 @@ function Build-ExtraSignArtifacts() {
Run-MSBuild "..\Compilers\Server\VBCSCompiler\VBCSCompiler.csproj" "/p:TargetFramework=netcoreapp2.0 /t:PublishWithoutBuilding"
Write-Host "Publishing MSBuildTask"
Run-MSBuild "..\Compilers\Core\MSBuildTask\MSBuildTask.csproj" "/p:TargetFramework=netcoreapp2.0 /t:PublishWithoutBuilding"
+ Write-Host "Building PortableFacades Swix"
+ Run-MSBuild "DevDivVsix\PortableFacades\PortableFacades.swixproj"
+ Write-Host "Building CompilersCodeAnalysis Swix"
+ Run-MSBuild "DevDivVsix\CompilersPackage\Microsoft.CodeAnalysis.Compilers.swixproj"
$dest = @($configDir)
foreach ($dir in $dest) {
diff --git a/build/scripts/tests.sh b/build/scripts/tests.sh
index 14e5066bac5e5..106d4f1bb8a55 100755
--- a/build/scripts/tests.sh
+++ b/build/scripts/tests.sh
@@ -22,7 +22,7 @@ if [[ "${runtime}" == "dotnet" ]]; then
target_framework=netcoreapp2.0
xunit_console="${nuget_dir}"/xunit.runner.console/"${xunit_console_version}"/tools/${target_framework}/xunit.console.dll
elif [[ "${runtime}" == "mono" ]]; then
- target_framework=net461
+ target_framework=net46
xunit_console="${nuget_dir}"/xunit.runner.console/"${xunit_console_version}"/tools/net452/xunit.console.exe
else
echo "Unknown runtime: ${runtime}"
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
index 502792ec560a2..d1b0547da16bb 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
@@ -2328,8 +2328,20 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin
// Unsafe code will always be allowed to escape.
return Binder.ExternalScope;
+ case BoundKind.AsOperator:
+ case BoundKind.AwaitExpression:
+ case BoundKind.ConditionalAccess:
+ case BoundKind.NullCoalescingOperator:
+ case BoundKind.ArrayAccess:
+ // only possible in error cases (if possible at all)
+ return scopeOfTheContainingExpression;
+
default:
- throw ExceptionUtilities.UnexpectedValue($"{expr.Kind} expression of {expr.Type} type");
+ // in error situations some unexpected nodes could make here
+ // returning "scopeOfTheContainingExpression" seems safer than throwing.
+ // we will still assert to make sure that all nodes are accounted for.
+ Debug.Assert(false, $"{expr.Kind} expression of {expr.Type} type");
+ return scopeOfTheContainingExpression;
}
}
@@ -2643,8 +2655,20 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
var operandExpression = ((BoundPointerIndirectionOperator)expr).Operand;
return CheckValEscape(operandExpression.Syntax, operandExpression, escapeFrom, escapeTo, checkingReceiver, diagnostics);
+ case BoundKind.AsOperator:
+ case BoundKind.AwaitExpression:
+ case BoundKind.ConditionalAccess:
+ case BoundKind.NullCoalescingOperator:
+ case BoundKind.ArrayAccess:
+ // only possible in error cases (if possible at all)
+ return false;
+
default:
- throw ExceptionUtilities.UnexpectedValue($"{expr.Kind} expression of {expr.Type} type");
+ // in error situations some unexpected nodes could make here
+ // returning "false" seems safer than throwing.
+ // we will still assert to make sure that all nodes are accounted for.
+ Debug.Assert(false, $"{expr.Kind} expression of {expr.Type} type");
+ return false;
#region "cannot produce ref-like values"
// case BoundKind.ThrowExpression:
@@ -2652,11 +2676,7 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// case BoundKind.ArgList:
// case BoundKind.RefTypeOperator:
// case BoundKind.AddressOfOperator:
-// case BoundKind.AsOperator:
// case BoundKind.TypeOfOperator:
-// case BoundKind.ArrayAccess:
-// case BoundKind.NullCoalescingOperator:
-// case BoundKind.AwaitExpression:
// case BoundKind.IsOperator:
// case BoundKind.SizeOfOperator:
// case BoundKind.DynamicMemberAccess:
@@ -2674,7 +2694,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// case BoundKind.NoPiaObjectCreationExpression:
// case BoundKind.BaseReference:
// case BoundKind.Literal:
-// case BoundKind.ConditionalAccess:
// case BoundKind.IsPatternExpression:
// case BoundKind.DeconstructionAssignmentOperator:
// case BoundKind.EventAccess:
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
index a83438d4e6d24..ce6fe2db6ef18 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
@@ -492,50 +492,61 @@ private BoundStatement BindLocalFunctionStatement(LocalFunctionStatementSyntax n
var hasErrors = localSymbol.ScopeBinder
.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);
- BoundBlock block;
+ BoundBlock blockBody = null;
+ BoundBlock expressionBody = null;
if (node.Body != null)
{
- block = BindEmbeddedBlock(node.Body, diagnostics);
+ blockBody = runAnalysis(BindEmbeddedBlock(node.Body, diagnostics), diagnostics);
+
+ if (node.ExpressionBody != null)
+ {
+ var expressionBodyDiagnostics = new DiagnosticBag();
+ expressionBody = runAnalysis(BindExpressionBodyAsBlock(node.ExpressionBody, expressionBodyDiagnostics), expressionBodyDiagnostics);
+ }
}
else if (node.ExpressionBody != null)
{
- block = BindExpressionBodyAsBlock(node.ExpressionBody, diagnostics);
+ expressionBody = runAnalysis(BindExpressionBodyAsBlock(node.ExpressionBody, diagnostics), diagnostics);
}
else
{
- block = null;
hasErrors = true;
diagnostics.Add(ErrorCode.ERR_LocalFunctionMissingBody, localSymbol.Locations[0], localSymbol);
}
- if (block != null)
- {
- localSymbol.ComputeReturnType();
-
- // Have to do ControlFlowPass here because in MethodCompiler, we don't call this for synthed methods
- // rather we go directly to LowerBodyOrInitializer, which skips over flow analysis (which is in CompileMethod)
- // (the same thing - calling ControlFlowPass.Analyze in the lowering - is done for lambdas)
- // It's a bit of code duplication, but refactoring would make things worse.
- var endIsReachable = ControlFlowPass.Analyze(localSymbol.DeclaringCompilation, localSymbol, block, diagnostics);
- if (endIsReachable)
- {
- if (ImplicitReturnIsOkay(localSymbol))
- {
- block = FlowAnalysisPass.AppendImplicitReturn(block, localSymbol);
- }
- else
- {
- diagnostics.Add(ErrorCode.ERR_ReturnExpected, localSymbol.Locations[0], localSymbol);
- }
- }
- }
+ Debug.Assert(blockBody != null || expressionBody != null || hasErrors);
localSymbol.GetDeclarationDiagnostics(diagnostics);
Symbol.CheckForBlockAndExpressionBody(
node.Body, node.ExpressionBody, node, diagnostics);
- return new BoundLocalFunctionStatement(node, localSymbol, block, hasErrors);
+ return new BoundLocalFunctionStatement(node, localSymbol, blockBody, expressionBody, hasErrors);
+
+ BoundBlock runAnalysis(BoundBlock block, DiagnosticBag blockDiagnostics)
+ {
+ if (block != null)
+ {
+ // Have to do ControlFlowPass here because in MethodCompiler, we don't call this for synthed methods
+ // rather we go directly to LowerBodyOrInitializer, which skips over flow analysis (which is in CompileMethod)
+ // (the same thing - calling ControlFlowPass.Analyze in the lowering - is done for lambdas)
+ // It's a bit of code duplication, but refactoring would make things worse.
+ var endIsReachable = ControlFlowPass.Analyze(localSymbol.DeclaringCompilation, localSymbol, block, blockDiagnostics);
+ if (endIsReachable)
+ {
+ if (ImplicitReturnIsOkay(localSymbol))
+ {
+ block = FlowAnalysisPass.AppendImplicitReturn(block, localSymbol);
+ }
+ else
+ {
+ blockDiagnostics.Add(ErrorCode.ERR_ReturnExpected, localSymbol.Locations[0], localSymbol);
+ }
+ }
+ }
+
+ return block;
+ }
}
private bool ImplicitReturnIsOkay(MethodSymbol method)
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundLocalFunctionStatement.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundLocalFunctionStatement.cs
new file mode 100644
index 0000000000000..0f217846e735b
--- /dev/null
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundLocalFunctionStatement.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.CodeAnalysis.CSharp
+{
+ internal partial class BoundLocalFunctionStatement
+ {
+ public BoundBlock Body { get => BlockBody ?? ExpressionBody; }
+ }
+}
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
index 12aa250318354..310bb982d3085 100644
--- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
@@ -742,7 +742,8 @@
-->
-
+
+
diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj
index 5fecdd14d284d..79064a00e6527 100644
--- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj
+++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/BasicExpressionCompilerTest.vbproj
@@ -7,7 +7,7 @@
AnyCPU
Library
Roslyn.ExpressionEvaluator.VisualBasic.ExpressionCompiler.UnitTests
- net461
+ net46
$(RoslynDesktopRuntimeIdentifier)
UnitTest
diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj b/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj
index 31380a44ca61b..96bb17f7b2b16 100644
--- a/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj
+++ b/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/BasicResultProviderTest.vbproj
@@ -7,7 +7,7 @@
AnyCPU
Library
Roslyn.ExpressionEvaluator.VisualBasic.ResultProvider.UnitTests
- net461
+ net46
$(RoslynDesktopRuntimeIdentifier)
Default
UnitTest
diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs b/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs
index c94812ab67804..881a84f93d3b2 100644
--- a/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs
+++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs
@@ -288,20 +288,29 @@ internal static string conversion_operator {
}
///
- /// Looks up a localized string similar to Convert 'for' to 'foreach'.
+ /// Looks up a localized string similar to Convert to 'for'.
///
- internal static string Convert_for_to_foreach {
+ internal static string Convert_to_for {
get {
- return ResourceManager.GetString("Convert_for_to_foreach", resourceCulture);
+ return ResourceManager.GetString("Convert_to_for", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Convert 'if' to 'switch'.
+ /// Looks up a localized string similar to Convert to 'foreach'.
///
- internal static string Convert_if_to_switch {
+ internal static string Convert_to_foreach {
get {
- return ResourceManager.GetString("Convert_if_to_switch", resourceCulture);
+ return ResourceManager.GetString("Convert_to_foreach", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Convert to 'switch'.
+ ///
+ internal static string Convert_to_switch {
+ get {
+ return ResourceManager.GetString("Convert_to_switch", resourceCulture);
}
}
diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
index 33729d573f118..dc26cb8b8afb6 100644
--- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
+++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
@@ -497,8 +497,8 @@
Add 'this.'
-
- Convert 'if' to 'switch'
+
+ Convert to 'switch'
Warning: Extracting a local function reference may produce invalid code
@@ -521,7 +521,10 @@
Add parentheses
-
- Convert 'for' to 'foreach'
+
+ Convert to 'foreach'
+
+
+ Convert to 'for'
\ No newline at end of file
diff --git a/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs
new file mode 100644
index 0000000000000..5399da0dfb16a
--- /dev/null
+++ b/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs
@@ -0,0 +1,161 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Composition;
+using System.Threading;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeRefactorings;
+using Microsoft.CodeAnalysis.ConvertForEachToFor;
+using Microsoft.CodeAnalysis.CSharp.CodeStyle.TypeStyle;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CSharp.ConvertForEachToFor
+{
+ [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpConvertForEachToForCodeRefactoringProvider)), Shared]
+ internal sealed class CSharpConvertForEachToForCodeRefactoringProvider :
+ AbstractConvertForEachToForCodeRefactoringProvider
+ {
+ protected override string Title => CSharpFeaturesResources.Convert_to_for;
+
+ protected override ForEachStatementSyntax GetForEachStatement(TextSpan selection, SyntaxToken token)
+ {
+ var foreachStatement = token.Parent.FirstAncestorOrSelf();
+ if (foreachStatement == null)
+ {
+ return null;
+ }
+
+ // support refactoring only if caret is in between "foreach" and ")"
+ var scope = TextSpan.FromBounds(foreachStatement.ForEachKeyword.Span.Start, foreachStatement.CloseParenToken.Span.End);
+ if (!scope.IntersectsWith(selection))
+ {
+ return null;
+ }
+
+ // check whether there is any comments between foreach and ) tokens
+ // if they do, we don't support conversion.
+ foreach (var trivia in foreachStatement.DescendantTrivia(n => n == foreachStatement || scope.Contains(n.FullSpan)))
+ {
+ if (trivia.Span.End <= scope.Start ||
+ scope.End <= trivia.Span.Start)
+ {
+ continue;
+ }
+
+ if (trivia.Kind() != SyntaxKind.WhitespaceTrivia &&
+ trivia.Kind() != SyntaxKind.EndOfLineTrivia)
+ {
+ // we don't know what to do with these comments
+ return null;
+ }
+ }
+
+ return foreachStatement;
+ }
+
+ protected override bool ValidLocation(ForEachInfo foreachInfo)
+ {
+ if (!foreachInfo.RequireCollectionStatement)
+ {
+ return true;
+ }
+
+ // for now, we don't support converting in embeded statement if
+ // new local declaration for collection is required.
+ // we can support this by using Introduce local variable service
+ // but the service is not currently written in a way that can be
+ // easily reused here.
+ return foreachInfo.ForEachStatement.Parent.IsKind(SyntaxKind.Block);
+ }
+
+ protected override (SyntaxNode start, SyntaxNode end) GetForEachBody(ForEachStatementSyntax foreachStatement)
+ => (foreachStatement.Statement, foreachStatement.Statement);
+
+ protected override void ConvertToForStatement(
+ SemanticModel model, ForEachInfo foreachInfo, SyntaxEditor editor, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var generator = editor.Generator;
+ var foreachStatement = foreachInfo.ForEachStatement;
+
+ var foreachCollectionExpression = foreachStatement.Expression;
+ var collectionVariable = GetCollectionVariableName(
+ model, generator, foreachInfo, foreachCollectionExpression, cancellationToken);
+
+ var collectionStatementType = foreachInfo.RequireExplicitCastInterface
+ ? generator.GetTypeExpression(foreachInfo.Options, foreachInfo.ExplicitCastInterface) :
+ generator.GetTypeExpression(foreachInfo.Options,
+ model.GetTypeInfo(foreachCollectionExpression).Type ?? model.Compilation.GetSpecialType(SpecialType.System_Object));
+
+ // first, see whether we need to introduce new statement to capture collection
+ IntroduceCollectionStatement(
+ model, foreachInfo, editor, collectionStatementType, foreachCollectionExpression, collectionVariable);
+
+ var indexVariable = CreateUniqueName(foreachInfo.SemanticFacts, model, foreachStatement.Statement, "i", cancellationToken);
+
+ // put variable statement in body
+ var bodyStatement = GetForLoopBody(generator, foreachInfo, collectionVariable, indexVariable);
+
+ // create for statement from foreach statement
+ var forStatement = SyntaxFactory.ForStatement(
+ SyntaxFactory.VariableDeclaration(
+ generator.GetTypeExpression(
+ foreachInfo.Options, model.Compilation.GetSpecialType(SpecialType.System_Int32)),
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.VariableDeclarator(
+ indexVariable.WithAdditionalAnnotations(RenameAnnotation.Create()),
+ argumentList: default,
+ SyntaxFactory.EqualsValueClause((ExpressionSyntax)generator.LiteralExpression(0))))),
+ SyntaxFactory.SeparatedList(),
+ (ExpressionSyntax)generator.LessThanExpression(
+ generator.IdentifierName(indexVariable),
+ generator.MemberAccessExpression(collectionVariable, foreachInfo.CountName)),
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.PostfixUnaryExpression(
+ SyntaxKind.PostIncrementExpression, SyntaxFactory.IdentifierName(indexVariable))),
+ bodyStatement);
+
+ if (!foreachInfo.RequireCollectionStatement)
+ {
+ // move comments before "foreach" keyword to "for". if collection statement is introduced,
+ // it should have attached to the new collection statement, so no need to do it here.
+ forStatement = forStatement.WithLeadingTrivia(foreachStatement.GetLeadingTrivia());
+ }
+
+ // replace close parenthese from "foreach" statement
+ forStatement = forStatement.WithCloseParenToken(foreachStatement.CloseParenToken);
+
+ editor.ReplaceNode(foreachStatement, forStatement);
+ }
+
+ private StatementSyntax GetForLoopBody(
+ SyntaxGenerator generator, ForEachInfo foreachInfo, SyntaxNode collectionVariableName, SyntaxToken indexVariable)
+ {
+ var foreachStatement = foreachInfo.ForEachStatement;
+ if (foreachStatement.Statement is EmptyStatementSyntax)
+ {
+ return foreachStatement.Statement;
+ }
+
+ var bodyBlock = foreachStatement.Statement is BlockSyntax block ? block : SyntaxFactory.Block(foreachStatement.Statement);
+ if (bodyBlock.Statements.Count > 0)
+ {
+ // create variable statement
+ var variableStatement = AddItemVariableDeclaration(
+ generator, generator.GetTypeExpression(foreachInfo.Options, foreachInfo.ForEachElementType),
+ foreachStatement.Identifier, foreachInfo.ForEachElementType, collectionVariableName, indexVariable);
+
+ bodyBlock = bodyBlock.InsertNodesBefore(
+ bodyBlock.Statements[0], SpecializedCollections.SingletonEnumerable(
+ variableStatement.WithAdditionalAnnotations(Formatter.Annotation)));
+ }
+
+ return bodyBlock;
+ }
+ }
+}
diff --git a/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs
index e3c9ae7d41693..156a242cfd06f 100644
--- a/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs
+++ b/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs
@@ -15,15 +15,15 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertForToForEach
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpConvertForToForEachCodeRefactoringProvider)), Shared]
internal class CSharpConvertForToForEachCodeRefactoringProvider :
AbstractConvertForToForEachCodeRefactoringProvider<
- StatementSyntax,
- ForStatementSyntax,
+ StatementSyntax,
+ ForStatementSyntax,
ExpressionSyntax,
MemberAccessExpressionSyntax,
TypeSyntax,
VariableDeclaratorSyntax>
{
protected override string GetTitle()
- => CSharpFeaturesResources.Convert_for_to_foreach;
+ => CSharpFeaturesResources.Convert_to_foreach;
protected override bool IsValidCursorPosition(ForStatementSyntax forStatement, int cursorPos)
{
@@ -44,7 +44,7 @@ protected override SyntaxList GetBodyStatements(ForStatementSyn
protected override bool TryGetForStatementComponents(
ForStatementSyntax forStatement,
out SyntaxToken iterationVariable, out ExpressionSyntax initializer,
- out MemberAccessExpressionSyntax memberAccess,
+ out MemberAccessExpressionSyntax memberAccess,
out ExpressionSyntax stepValueExpressionOpt,
CancellationToken cancellationToken)
{
@@ -100,25 +100,25 @@ private static bool TryGetStepValue(
ExpressionSyntax operand;
switch (incrementor.Kind())
{
- case SyntaxKind.PostIncrementExpression:
- operand = ((PostfixUnaryExpressionSyntax)incrementor).Operand;
- stepValue = default;
- break;
-
- case SyntaxKind.PreIncrementExpression:
- operand = ((PrefixUnaryExpressionSyntax)incrementor).Operand;
- stepValue = default;
- break;
-
- case SyntaxKind.AddAssignmentExpression:
- var assignment = (AssignmentExpressionSyntax)incrementor;
- operand = assignment.Left;
- stepValue = assignment.Right;
- break;
-
- default:
- stepValue = null;
- return false;
+ case SyntaxKind.PostIncrementExpression:
+ operand = ((PostfixUnaryExpressionSyntax)incrementor).Operand;
+ stepValue = default;
+ break;
+
+ case SyntaxKind.PreIncrementExpression:
+ operand = ((PrefixUnaryExpressionSyntax)incrementor).Operand;
+ stepValue = default;
+ break;
+
+ case SyntaxKind.AddAssignmentExpression:
+ var assignment = (AssignmentExpressionSyntax)incrementor;
+ operand = assignment.Left;
+ stepValue = assignment.Right;
+ break;
+
+ default:
+ stepValue = null;
+ return false;
}
return operand is IdentifierNameSyntax identifierName &&
@@ -126,24 +126,11 @@ private static bool TryGetStepValue(
}
protected override SyntaxNode ConvertForNode(
- ForStatementSyntax forStatement, TypeSyntax typeNode,
+ ForStatementSyntax forStatement, TypeSyntax typeNode,
SyntaxToken foreachIdentifier, ExpressionSyntax collectionExpression,
ITypeSymbol iterationVariableType, OptionSet optionSet)
{
- if (typeNode == null)
- {
- // types are not apparent in foreach statements.
- var isBuiltInTypeContext = TypeStyleHelper.IsBuiltInType(iterationVariableType);
- if (TypeStyleHelper.IsImplicitStylePreferred(
- optionSet, isBuiltInTypeContext, isTypeApparentContext: false))
- {
- typeNode = SyntaxFactory.IdentifierName("var");
- }
- else
- {
- typeNode = (TypeSyntax)CSharpSyntaxGenerator.Instance.TypeExpression(iterationVariableType);
- }
- }
+ typeNode = typeNode ?? CSharpSyntaxGenerator.Instance.GetTypeExpression(optionSet, iterationVariableType);
return SyntaxFactory.ForEachStatement(
SyntaxFactory.Token(SyntaxKind.ForEachKeyword).WithTriviaFrom(forStatement.ForKeyword),
diff --git a/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.cs
index e501566d531b6..bd463feaf7fb3 100644
--- a/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.cs
+++ b/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.cs
@@ -25,7 +25,7 @@ public CSharpAnalyzer(ISyntaxFactsService syntaxFacts, SemanticModel semanticMod
{
}
- protected override string Title => CSharpFeaturesResources.Convert_if_to_switch;
+ protected override string Title => CSharpFeaturesResources.Convert_to_switch;
protected override IPattern CreatePatternFromExpression(ExpressionSyntax operand)
{
diff --git a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs
new file mode 100644
index 0000000000000..ac3c46b13a332
--- /dev/null
+++ b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs
@@ -0,0 +1,978 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis.ConvertLinq;
+using Microsoft.CodeAnalysis.CSharp.Extensions;
+using Microsoft.CodeAnalysis.CSharp.Symbols;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.LanguageServices;
+using Microsoft.CodeAnalysis.Operations;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Shared.Utilities;
+using Microsoft.CodeAnalysis.Simplification;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.CSharp.ConvertLinq
+{
+ internal sealed class CSharpConvertLinqQueryToForEachProvider : AbstractConvertLinqQueryToForEachProvider
+ {
+ private static readonly TypeSyntax VarNameIdentifier = SyntaxFactory.IdentifierName("var");
+
+ protected override string Title => CSharpFeaturesResources.Convert_to_foreach;
+
+ protected override bool TryConvert(
+ QueryExpressionSyntax queryExpression,
+ SemanticModel semanticModel,
+ ISemanticFactsService semanticFacts,
+ CancellationToken cancellationToken,
+ out DocumentUpdateInfo documentUpdateInfo)
+ => new Converter(semanticModel, semanticFacts, queryExpression, cancellationToken).TryConvert(out documentUpdateInfo);
+
+ ///
+ /// Finds a node for the span and checks that it is either a QueryExpressionSyntax or a QueryExpressionSyntax argument within ArgumentSyntax.
+ ///
+ protected override QueryExpressionSyntax FindNodeToRefactor(SyntaxNode root, TextSpan span)
+ {
+ var node = root.FindNode(span);
+ return node as QueryExpressionSyntax ?? (node is ArgumentSyntax argument ? argument.Expression as QueryExpressionSyntax : default);
+ }
+
+ private sealed class Converter
+ {
+ private readonly SemanticModel _semanticModel;
+ private readonly ISemanticFactsService _semanticFacts;
+ private readonly CancellationToken _cancellationToken;
+ private readonly QueryExpressionSyntax _source;
+ private readonly List _introducedLocalNames;
+
+ public Converter(SemanticModel semanticModel, ISemanticFactsService semanticFacts, QueryExpressionSyntax source, CancellationToken cancellationToken)
+ {
+ _semanticModel = semanticModel;
+ _semanticFacts = semanticFacts;
+ _source = source;
+ _introducedLocalNames = new List();
+ _cancellationToken = cancellationToken;
+ }
+
+ public bool TryConvert(out DocumentUpdateInfo documentUpdateInfo)
+ {
+ // Do not try refactoring queries with comments or conditional compilation in them.
+ // We can consider supporting queries with comments in the future.
+ if (_source.DescendantTrivia().Any(trivia => trivia.MatchesKind(
+ SyntaxKind.SingleLineCommentTrivia,
+ SyntaxKind.MultiLineCommentTrivia,
+ SyntaxKind.MultiLineDocumentationCommentTrivia) ||
+ _source.ContainsDirectives))
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ // Bail out if there is no chance to convert it even with a local function.
+ if (!CanTryConvertToLocalFunction() ||
+ !TryCreateStackFromQueryExpression(out QueryExpressionProcessingInfo queryExpressionProcessingInfo))
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ // GetDiagnostics is expensive. Move it to the end if there were no bail outs from the algorithm.
+ // TODO likely adding more semantic checks will perform checks we expect from GetDiagnostics
+ // We may consider removing GetDiagnostics.
+ // https://github.com/dotnet/roslyn/issues/25639
+ if ((TryConvertInternal(queryExpressionProcessingInfo, out documentUpdateInfo) ||
+ TryReplaceWithLocalFunction(queryExpressionProcessingInfo, out documentUpdateInfo)) && // second attempt: at least to a local function
+ !_semanticModel.GetDiagnostics(_source.Span, _cancellationToken).Any(diagnostic => diagnostic.DefaultSeverity == DiagnosticSeverity.Error))
+ {
+ if (!documentUpdateInfo.Source.IsParentKind(SyntaxKind.Block) &&
+ documentUpdateInfo.Destinations.Length > 1)
+
+ documentUpdateInfo = new DocumentUpdateInfo(documentUpdateInfo.Source, SyntaxFactory.Block(documentUpdateInfo.Destinations));
+ return true;
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ private StatementSyntax ProcessClause(
+ CSharpSyntaxNode node,
+ StatementSyntax statement,
+ bool isLastClause,
+ bool hasExtraDeclarations,
+ out StatementSyntax extraStatementToAddAbove)
+ {
+ extraStatementToAddAbove = default;
+ switch (node.Kind())
+ {
+ case SyntaxKind.WhereClause:
+ return SyntaxFactory.Block(SyntaxFactory.IfStatement(((WhereClauseSyntax)node).Condition.WithAdditionalAnnotations(Simplifier.Annotation).WithoutTrivia(), statement));
+ case SyntaxKind.FromClause:
+ var fromClause = (FromClauseSyntax)node;
+
+ // If we are processing the first from and
+ // there were joins and some evaluations were moved into declarations above the foreach
+ // Check if the declaration on the first fromclause should be moved for the evaluation above declarations already moved upfront.
+ ExpressionSyntax expression1;
+ if (isLastClause && hasExtraDeclarations && !IsLocalOrParameterSymbol(_source.FromClause.Expression))
+ {
+ string expressionName = _semanticFacts.GenerateNameForExpression(
+ _semanticModel,
+ fromClause.Expression,
+ capitalize: false,
+ _cancellationToken);
+ SyntaxToken variable = GetFreeSymbolNameAndMarkUsed(expressionName);
+ extraStatementToAddAbove = CreateLocalDeclarationStatement(variable, fromClause.Expression, generateTypeFromExpression: false);
+ expression1 = SyntaxFactory.IdentifierName(variable);
+ }
+ else
+ {
+ expression1 = fromClause.Expression.WithoutTrivia();
+ }
+
+ return SyntaxFactory.ForEachStatement(
+ fromClause.Type ?? VarNameIdentifier,
+ fromClause.Identifier,
+ expression1,
+ WrapWithBlock(statement));
+ case SyntaxKind.LetClause:
+ var letClause = (LetClauseSyntax)node;
+ return AddToBlockTop(CreateLocalDeclarationStatement(letClause.Identifier, letClause.Expression, generateTypeFromExpression: false), statement);
+ case SyntaxKind.JoinClause:
+ var joinClause = (JoinClauseSyntax)node;
+ if (joinClause.Into != null)
+ {
+ // This must be caught on the validation step. Therefore, here is an exception.
+ throw new ArgumentException("GroupJoin is not supported");
+ }
+ else
+ {
+ ExpressionSyntax expression2;
+ if (IsLocalOrParameterSymbol(joinClause.InExpression))
+ {
+ expression2 = joinClause.InExpression;
+ }
+ else
+ {
+ // Input: var q = from x in XX() from z in ZZ() join y in YY() on x equals y select x + y;
+ // Add
+ // var xx = XX();
+ // var yy = YY();
+ // Do not add for ZZ()
+ string expressionName = _semanticFacts.GenerateNameForExpression(
+ _semanticModel,
+ joinClause.InExpression,
+ capitalize: false,
+ _cancellationToken);
+ SyntaxToken variable = GetFreeSymbolNameAndMarkUsed(expressionName);
+ extraStatementToAddAbove = CreateLocalDeclarationStatement(variable, joinClause.InExpression, generateTypeFromExpression: false);
+
+ // Replace YY() with yy declared above.
+ expression2 = SyntaxFactory.IdentifierName(variable);
+ }
+
+ // Output for the join
+ // var yy == YY(); this goes to extraStatementToAddAbove
+ // ...
+ // foreach (var y in yy)
+ // {
+ // if (object.Equals(x, y))
+ // {
+ // ...
+ return SyntaxFactory.Block(
+ SyntaxFactory.ForEachStatement(
+ joinClause.Type ?? VarNameIdentifier,
+ joinClause.Identifier,
+ expression2,
+ SyntaxFactory.Block(
+ SyntaxFactory.IfStatement(
+ SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)),
+ SyntaxFactory.IdentifierName(nameof(object.Equals))),
+ SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(
+ new[] {
+ SyntaxFactory.Argument(joinClause.LeftExpression),
+ SyntaxFactory.Argument(joinClause.RightExpression.WithoutTrailingTrivia())}))),
+ statement)))).WithAdditionalAnnotations(Simplifier.Annotation);
+ }
+ case SyntaxKind.SelectClause:
+ // This is not the latest Select in the Query Expression
+ // There is a QueryBody with the Continuation as a parent.
+ var selectClause = (SelectClauseSyntax)node;
+ var identifier = ((QueryBodySyntax)selectClause.Parent).Continuation.Identifier;
+ return AddToBlockTop(CreateLocalDeclarationStatement(identifier, selectClause.Expression, generateTypeFromExpression: true), statement);
+ default:
+ throw new ArgumentException($"Unexpected node kind {node.Kind().ToString()}");
+ }
+ }
+
+ private bool TryConvertInternal(QueryExpressionProcessingInfo queryExpressionProcessingInfo, out DocumentUpdateInfo documentUpdateInfo)
+ {
+ // (from a in b select a);
+ SyntaxNode parent = _source.WalkUpParentheses().Parent;
+
+ switch (parent.Kind())
+ {
+ // return from a in b select a;
+ case SyntaxKind.ReturnStatement:
+ return TryConvertIfInReturnStatement((ReturnStatementSyntax)parent, queryExpressionProcessingInfo, out documentUpdateInfo);
+ // foreach(var x in from a in b select a)
+ case SyntaxKind.ForEachStatement:
+ return TryConvertIfInForEach((ForEachStatementSyntax)parent, queryExpressionProcessingInfo, out documentUpdateInfo);
+ // (from a in b select a).ToList(), (from a in b select a).Count(), etc.
+ case SyntaxKind.SimpleMemberAccessExpression:
+ return TryConvertIfInMemberAccessExpression((MemberAccessExpressionSyntax)parent, queryExpressionProcessingInfo, out documentUpdateInfo);
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ ///
+ /// Checks if the location of the query expression allows to convert it at least to a local function.
+ /// It still does not guarantees that the conversion can be performed. There can be bail outs of later stages.
+ ///
+ ///
+ private bool CanTryConvertToLocalFunction()
+ {
+ SyntaxNode currentNode = _source;
+ while (currentNode != null)
+ {
+ if (currentNode is StatementSyntax) { return true; }
+ if (currentNode is ExpressionSyntax ||
+ currentNode is ArgumentSyntax ||
+ currentNode is ArgumentListSyntax ||
+ currentNode is EqualsValueClauseSyntax ||
+ currentNode is VariableDeclaratorSyntax ||
+ currentNode is VariableDeclarationSyntax)
+ {
+ currentNode = currentNode.Parent;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private bool TryConvertIfInMemberAccessExpression(
+ MemberAccessExpressionSyntax memberAccessExpression,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo,
+ out DocumentUpdateInfo documentUpdateInfo)
+ {
+ if (memberAccessExpression.Parent is InvocationExpressionSyntax invocationExpression)
+ {
+ // This also covers generic names (i.e. with type arguments) like 'ToList'.
+ // The ValueText is still just 'ToList'.
+ switch (memberAccessExpression.Name.Identifier.ValueText)
+ {
+ case nameof(Enumerable.ToList):
+ return TryConvertIfInToListInvocation(invocationExpression, queryExpressionProcessingInfo, out documentUpdateInfo);
+ case nameof(Enumerable.Count):
+ return TryConvertIfInCountInvocation(invocationExpression, queryExpressionProcessingInfo, out documentUpdateInfo);
+ }
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ private bool TryConvertIfInCountInvocation(
+ InvocationExpressionSyntax invocationExpression,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo,
+ out DocumentUpdateInfo documentUpdateInfo)
+ {
+ if (_semanticModel.GetSymbolInfo(invocationExpression, _cancellationToken).Symbol is IMethodSymbol methodSymbol &&
+ methodSymbol.Parameters.Length == 0 &&
+ methodSymbol.ReturnType?.SpecialType == SpecialType.System_Int32 &&
+ methodSymbol.RefKind == RefKind.None)
+ {
+ // before var count = (from a in b select a).Count();
+ // after
+ // var count = 0;
+ // foreach (var a in b)
+ // {
+ // count++;
+ // }
+ return TryConvertIfInInvocation(
+ invocationExpression,
+ queryExpressionProcessingInfo,
+ IsInt,
+ (variableIdentifier, expression) => SyntaxFactory.ExpressionStatement(
+ SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, variableIdentifier)), // Generating 'count++'
+ SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0)), // count = 0
+ variableName: "count",
+ out documentUpdateInfo);
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ private bool TryConvertIfInToListInvocation(
+ InvocationExpressionSyntax invocationExpression,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo,
+ out DocumentUpdateInfo documentUpdateInfo)
+ {
+ // before var list = (from a in b select a).ToList();
+ // after
+ // var list = new List();
+ // foreach (var a in b)
+ // {
+ // list.Add(a)
+ // }
+ if (_semanticModel.GetSymbolInfo(invocationExpression, _cancellationToken).Symbol is IMethodSymbol methodSymbol &&
+ methodSymbol.RefKind == RefKind.None &&
+ IsList(methodSymbol.ReturnType) &&
+ methodSymbol.Parameters.Length == 0)
+ {
+ return TryConvertIfInInvocation(
+ invocationExpression,
+ queryExpressionProcessingInfo,
+ IsList,
+ (listIdentifier, expression) => SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ listIdentifier,
+ SyntaxFactory.IdentifierName(nameof(IList.Add))),
+ SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(expression))))),
+ SyntaxFactory.ObjectCreationExpression(
+ methodSymbol.GenerateReturnTypeSyntax().WithAdditionalAnnotations(Simplifier.Annotation),
+ SyntaxFactory.ArgumentList(),
+ initializer: null),
+ variableName: "list",
+ out documentUpdateInfo);
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ private bool IsInt(ITypeSymbol typeSymbol)
+ => typeSymbol.SpecialType == SpecialType.System_Int32;
+
+ private bool IsList(ITypeSymbol typeSymbol)
+ => Equals(typeSymbol.OriginalDefinition, _semanticModel.Compilation.GetTypeByMetadataName(typeof(List<>).FullName));
+
+ private bool TryConvertIfInInvocation(
+ InvocationExpressionSyntax invocationExpression,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo,
+ Func typeCheckMethod,
+ Func leafExpressionCreationMethod,
+ ExpressionSyntax initializer,
+ string variableName,
+ out DocumentUpdateInfo documentUpdateInfo)
+ {
+ var parentStatement = invocationExpression.GetAncestorOrThis();
+ if (parentStatement != null)
+ {
+ if (TryConvertIfInInvocationInternal(
+ invocationExpression,
+ typeCheckMethod,
+ parentStatement,
+ initializer,
+ variableName,
+ out var variable,
+ out var nodesBefore,
+ out var nodesAfter))
+ {
+ var statements = GenerateStatements(expression => leafExpressionCreationMethod(variable, expression), queryExpressionProcessingInfo);
+ var list = new List();
+ list.AddRange(nodesBefore);
+ list.AddRange(statements);
+ list.AddRange(nodesAfter);
+ documentUpdateInfo = new DocumentUpdateInfo(parentStatement, list);
+ return true;
+ }
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ private bool TryConvertIfInInvocationInternal(
+ InvocationExpressionSyntax invocationExpression,
+ Func typeCheckMethod,
+ StatementSyntax parentStatement,
+ ExpressionSyntax initializer,
+ string variableName,
+ out ExpressionSyntax variable,
+ out StatementSyntax[] nodesBefore,
+ out StatementSyntax[] nodesAfter)
+ {
+ var invocationParent = invocationExpression.WalkUpParentheses().Parent;
+ var symbolName = GetFreeSymbolNameAndMarkUsed(variableName);
+
+ void Convert(
+ ExpressionSyntax variableExpression,
+ ExpressionSyntax expressionToVerifyType,
+ bool checkForLocalOrParameter,
+ out ExpressionSyntax variableLocal,
+ out StatementSyntax[] nodesBeforeLocal,
+ out StatementSyntax[] nodesAfterLocal)
+ {
+ // Check that we can re-use the local variable or parameter
+ if (typeCheckMethod(_semanticModel.GetTypeInfo(expressionToVerifyType, _cancellationToken).Type) &&
+ (!checkForLocalOrParameter || IsLocalOrParameterSymbol(variableExpression)))
+ {
+ // before
+ // a = (from a in b select a).ToList(); or var a = (from a in b select a).ToList()
+ // after
+ // a = new List(); or var a = new List();
+ // foreach(...)
+ variableLocal = variableExpression;
+ nodesBeforeLocal = new[] { parentStatement.ReplaceNode(invocationExpression, initializer.WithAdditionalAnnotations(Simplifier.Annotation)) };
+ nodesAfterLocal = new StatementSyntax[] { };
+ }
+ else
+ {
+ // before
+ // IReadOnlyList a = (from a in b select a).ToList(); or an assignment
+ // after
+ // var list = new List(); or assignment
+ // foreach(...)
+ // IReadOnlyList a = list;
+ variableLocal = SyntaxFactory.IdentifierName(symbolName);
+ nodesBeforeLocal = new[] { CreateLocalDeclarationStatement(symbolName, initializer, generateTypeFromExpression: false) };
+ nodesAfterLocal = new StatementSyntax[] { parentStatement.ReplaceNode(invocationExpression, variableLocal.WithAdditionalAnnotations(Simplifier.Annotation)) };
+ }
+ }
+
+ switch (invocationParent.Kind())
+ {
+ case SyntaxKind.EqualsValueClause:
+ // Avoid for(int i = (from x in a select x).Count(); i < 10; i++)
+ if (invocationParent.IsParentKind(SyntaxKind.VariableDeclarator, SyntaxKind.VariableDeclaration, SyntaxKind.LocalDeclarationStatement) &&
+ // Avoid int i = (from x in a select x).Count(), j = i;
+ ((VariableDeclarationSyntax)invocationParent.Parent.Parent).Variables.Count == 1)
+ {
+ var variableDeclarator = ((VariableDeclaratorSyntax)invocationParent.Parent);
+ Convert(
+ SyntaxFactory.IdentifierName(variableDeclarator.Identifier),
+ ((VariableDeclarationSyntax)variableDeclarator.Parent).Type,
+ checkForLocalOrParameter: false,
+ out variable,
+ out nodesBefore,
+ out nodesAfter);
+ return true;
+ }
+
+ break;
+ case SyntaxKind.SimpleAssignmentExpression:
+ var assignmentExpression = (AssignmentExpressionSyntax)invocationParent;
+ if (assignmentExpression.Right.WalkDownParentheses() == invocationExpression)
+ {
+ Convert(
+ assignmentExpression.Left,
+ assignmentExpression.Left,
+ checkForLocalOrParameter: true,
+ out variable,
+ out nodesBefore,
+ out nodesAfter);
+ return true;
+ }
+
+ break;
+ case SyntaxKind.ReturnStatement:
+ // before return (from a in b select a).ToList();
+ // after var list = new List();
+ // foreach(...)
+ // return list;
+ variable = SyntaxFactory.IdentifierName(symbolName);
+ nodesBefore = new[] { CreateLocalDeclarationStatement(symbolName, initializer, generateTypeFromExpression: false) };
+ nodesAfter = new[] { SyntaxFactory.ReturnStatement(variable).WithAdditionalAnnotations(Simplifier.Annotation) };
+ return true;
+ // SyntaxKind.Argument:
+ // SyntaxKind.ArrowExpressionClause is not supported
+ }
+
+ // Will still try to replace with a local function above.
+ nodesBefore = default;
+ nodesAfter = default;
+ variable = default;
+ return false;
+ }
+
+ private LocalDeclarationStatementSyntax CreateLocalDeclarationStatement(
+ SyntaxToken identifier,
+ ExpressionSyntax expression,
+ bool generateTypeFromExpression)
+ {
+ var typeSyntax = generateTypeFromExpression ?
+ _semanticModel.GetTypeInfo(expression, _cancellationToken).ConvertedType.GenerateTypeSyntax() :
+ VarNameIdentifier;
+ return SyntaxFactory.LocalDeclarationStatement(
+ SyntaxFactory.VariableDeclaration(
+ typeSyntax,
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.VariableDeclarator(
+ identifier,
+ argumentList: null,
+ SyntaxFactory.EqualsValueClause(expression))))).WithAdditionalAnnotations(Simplifier.Annotation);
+ }
+
+ private bool TryReplaceWithLocalFunction(QueryExpressionProcessingInfo queryExpressionProcessingInfo, out DocumentUpdateInfo documentUpdateInfo)
+ {
+ var parentStatement = _source.GetAncestorOrThis();
+ if (parentStatement == null)
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ // before statement ... from a in select b ...
+ // after
+ // IEnumerable localFunction()
+ // {
+ // foreach(var a in b)
+ // {
+ // yield return a;
+ // }
+ // }
+ // statement ... localFunction();
+ var returnTypeInfo = _semanticModel.GetTypeInfo(_source, _cancellationToken);
+ ITypeSymbol returnedType;
+
+ if (returnTypeInfo.Type.OriginalDefinition?.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)
+ {
+ returnedType = returnTypeInfo.Type;
+ }
+ else
+ {
+ if (returnTypeInfo.ConvertedType.OriginalDefinition?.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)
+ {
+ returnedType = returnTypeInfo.ConvertedType;
+ }
+ else
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+ }
+
+ StatementSyntax internalNodeMethod(ExpressionSyntax expression)
+ => SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, expression);
+
+ var statements = GenerateStatements(internalNodeMethod, queryExpressionProcessingInfo);
+ string localFunctionNamePrefix = _semanticFacts.GenerateNameForExpression(
+ _semanticModel,
+ _source,
+ capitalize: false,
+ _cancellationToken);
+ SyntaxToken localFunctionToken = GetFreeSymbolNameAndMarkUsed(localFunctionNamePrefix);
+ var localFunctionDeclaration = SyntaxFactory.LocalFunctionStatement(
+ modifiers: default,
+ returnType: returnedType.GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.Annotation),
+ identifier: localFunctionToken,
+ typeParameterList: null,
+ parameterList: SyntaxFactory.ParameterList(),
+ constraintClauses: default,
+ body: SyntaxFactory.Block(
+ SyntaxFactory.Token(
+ SyntaxFactory.TriviaList(),
+ SyntaxKind.OpenBraceToken,
+ SyntaxFactory.TriviaList(SyntaxFactory.EndOfLine(Environment.NewLine))),
+ SyntaxFactory.List(statements),
+ SyntaxFactory.Token(SyntaxKind.CloseBraceToken)),
+ expressionBody: null);
+
+ var localFunctionInvocation = SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(localFunctionToken)).WithAdditionalAnnotations(Simplifier.Annotation);
+ StatementSyntax newParentExpressionStatement = parentStatement.ReplaceNode(_source.WalkUpParentheses(), localFunctionInvocation.WithAdditionalAnnotations(Simplifier.Annotation));
+ documentUpdateInfo = new DocumentUpdateInfo(parentStatement, new[] { localFunctionDeclaration, newParentExpressionStatement });
+ return true;
+ }
+
+ private SyntaxToken GetFreeSymbolNameAndMarkUsed(string prefix)
+ {
+ var freeToken = _semanticFacts.GenerateUniqueName(_semanticModel, _source, containerOpt: null, baseName: prefix, _introducedLocalNames, _cancellationToken);
+ _introducedLocalNames.Add(freeToken.ValueText);
+ return freeToken;
+ }
+
+ private bool TryConvertIfInForEach(
+ ForEachStatementSyntax forEachStatement,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo,
+ out DocumentUpdateInfo documentUpdateInfo)
+ {
+ // before foreach(var x in from a in b select a)
+
+ if (forEachStatement.Expression.WalkDownParentheses() != _source)
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ // check that the body of the forEach does not contain any identifiers from the query
+ foreach (var identifierName in queryExpressionProcessingInfo.IdentifierNames)
+ {
+ // Identifier from the foreach can already be in scope of the foreach statement.
+ if (forEachStatement.Identifier.ValueText != identifierName)
+ {
+ if (_semanticFacts.GenerateUniqueName(
+ _semanticModel,
+ location: forEachStatement.Statement,
+ containerOpt: forEachStatement.Statement,
+ baseName: identifierName,
+ usedNames: Enumerable.Empty(),
+ _cancellationToken).ValueText != identifierName)
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+ }
+ }
+
+ // If query does not contains identifier with the same name as declared in the foreach,
+ // declare this identifier in the body.
+ if (!queryExpressionProcessingInfo.ContainsIdentifier(forEachStatement.Identifier))
+ {
+ documentUpdateInfo = ConvertIfInToForeachWithExtraVariableDeclaration(forEachStatement, queryExpressionProcessingInfo);
+ return true;
+ }
+ else
+ {
+ // The last select expression in the query returns this identifier:
+ // foreach(var thisIdentifier in from ....... select thisIdentifier)
+ // if thisIdentifier in foreach is var, it is OK
+ // if a type is specified for thisIdentifier in forEach, check that the type is the same as in the select expression
+ // foreach(MyType thisIdentifier in from ....... from MyType thisIdentifier ... select thisIdentifier)
+
+ // The last clause in query stack must be SelectClauseSyntax.
+ var lastSelectExpression = ((SelectClauseSyntax)queryExpressionProcessingInfo.Stack.Peek()).Expression;
+ if (lastSelectExpression is IdentifierNameSyntax identifierName &&
+ forEachStatement.Identifier.ValueText == identifierName.Identifier.ValueText &&
+ queryExpressionProcessingInfo.IdentifierNames.Contains(identifierName.Identifier.ValueText))
+ {
+ var forEachStatementTypeSymbolType = _semanticModel.GetTypeInfo(forEachStatement.Type, _cancellationToken).Type;
+ var lastSelectExpressionTypeInfo = _semanticModel.GetTypeInfo(lastSelectExpression, _cancellationToken);
+ if (Equals(lastSelectExpressionTypeInfo.ConvertedType, lastSelectExpressionTypeInfo.Type) &&
+ Equals(lastSelectExpressionTypeInfo.ConvertedType, forEachStatementTypeSymbolType))
+ {
+ documentUpdateInfo = ConvertIfInToForeachWithoutExtraVariableDeclaration(forEachStatement, queryExpressionProcessingInfo);
+ return true;
+ }
+ }
+ }
+
+ // in all other cases try to replace with a local function - this is called above.
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ private DocumentUpdateInfo ConvertIfInToForeachWithExtraVariableDeclaration(
+ ForEachStatementSyntax forEachStatement,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo)
+ {
+ // before foreach(var x in from ... a) { dosomething(x); }
+ // after
+ // foreach (var a in ...)
+ // ...
+ // {
+ // var x = a;
+ // dosomething(x);
+ // }
+ var statements = GenerateStatements(
+ expression => AddToBlockTop(SyntaxFactory.LocalDeclarationStatement(
+ SyntaxFactory.VariableDeclaration(
+ forEachStatement.Type,
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.VariableDeclarator(
+ forEachStatement.Identifier,
+ argumentList: null,
+ SyntaxFactory.EqualsValueClause(expression))))),
+ forEachStatement.Statement).WithAdditionalAnnotations(Formatter.Annotation), queryExpressionProcessingInfo);
+ return new DocumentUpdateInfo(forEachStatement, statements);
+ }
+
+ private DocumentUpdateInfo ConvertIfInToForeachWithoutExtraVariableDeclaration(
+ ForEachStatementSyntax forEachStatement,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo)
+ {
+ // before
+ // foreach (var a in from a in b where a > 5 select a)
+ // {
+ // dosomething(a);
+ // }
+ // after
+ // foreach (var a in b)
+ // {
+ // if (a > 5)
+ // {
+ // dosomething(a);
+ // }
+ // }
+ var statements = GenerateStatements(
+ expression => forEachStatement.Statement.WithAdditionalAnnotations(Formatter.Annotation),
+ queryExpressionProcessingInfo);
+ return new DocumentUpdateInfo(forEachStatement, statements);
+ }
+
+ private bool TryConvertIfInReturnStatement(
+ ReturnStatementSyntax returnStatement,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo,
+ out DocumentUpdateInfo documentUpdateInfo)
+ {
+ // The conversion requires yield return which cannot be added to lambdas and anonymous method declarations.
+ if (IsWithinImmediateLambdaOrAnonymousMethod(returnStatement))
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ var memberDeclarationNode = FindParentMemberDeclarationNode(returnStatement, out var declaredSymbol);
+ if (!(declaredSymbol is IMethodSymbol methodSymbol))
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ if (methodSymbol.ReturnType.OriginalDefinition?.SpecialType != SpecialType.System_Collections_Generic_IEnumerable_T)
+ {
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ // if there are more than one return in the method, convert to local funciton.
+ if (memberDeclarationNode.DescendantNodes().OfType().Count() == 1)
+ {
+ // before: return from a in b select a;
+ // after:
+ // foreach(var a in b)
+ // {
+ // yield return a;
+ // }
+ //
+ // yield break;
+ var statements = GenerateStatements((ExpressionSyntax expression)
+ => SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, expression), queryExpressionProcessingInfo);
+
+ // add an yield break to avoid throws after the return.
+ var yieldBreakStatement = SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement);
+ documentUpdateInfo = new DocumentUpdateInfo(returnStatement, statements.Concat(new[] { yieldBreakStatement }));
+ return true;
+ }
+
+ documentUpdateInfo = default;
+ return false;
+ }
+
+ // We may assume that the query is defined within a method, field, property and so on and it is declare just once.
+ private SyntaxNode FindParentMemberDeclarationNode(SyntaxNode node, out ISymbol declaredSymbol)
+ {
+ declaredSymbol = _semanticModel.GetEnclosingSymbol(node.SpanStart, _cancellationToken);
+ return declaredSymbol.DeclaringSyntaxReferences.Single().GetSyntax();
+ }
+
+ private bool TryCreateStackFromQueryExpression(out QueryExpressionProcessingInfo queryExpressionProcessingInfo)
+ {
+ queryExpressionProcessingInfo = new QueryExpressionProcessingInfo(_source.FromClause);
+ return TryProcessQueryBody(_source.Body, queryExpressionProcessingInfo);
+ }
+
+ private StatementSyntax[] GenerateStatements(
+ Func leafExpressionCreationMethod,
+ QueryExpressionProcessingInfo queryExpressionProcessingInfo)
+ {
+ StatementSyntax statement = default;
+ var stack = queryExpressionProcessingInfo.Stack;
+ // Executes syntax building methods from bottom to the top of the tree.
+ // Process last clause
+ if (stack.Any())
+ {
+ var node = stack.Pop();
+ if (node is SelectClauseSyntax selectClause)
+ {
+ statement = WrapWithBlock(leafExpressionCreationMethod(selectClause.Expression));
+ }
+ else
+ {
+ throw new ArgumentException("Last node must me the select clause");
+ }
+ }
+
+ // Process all other clauses
+ List statements = new List();
+ while (stack.Any())
+ {
+ statement = ProcessClause(
+ stack.Pop(),
+ statement,
+ isLastClause: !stack.Any(),
+ hasExtraDeclarations: statements.Any(),
+ out StatementSyntax extraStatement);
+ if (extraStatement != null)
+ {
+ statements.Add(extraStatement);
+ }
+ }
+
+ // The stack was processed in the reverse order, but the extra statements should be provided in the direct order.
+ statements.Reverse();
+ statements.Add(statement.WithAdditionalAnnotations(Simplifier.Annotation));
+ return statements.ToArray();
+ }
+
+ private bool TryProcessQueryBody(QueryBodySyntax queryBody, QueryExpressionProcessingInfo queryExpressionProcessingInfo)
+ {
+ do
+ {
+ foreach (var queryClause in queryBody.Clauses)
+ {
+ switch (queryClause.Kind())
+ {
+ case SyntaxKind.WhereClause:
+ queryExpressionProcessingInfo.Add(queryClause);
+ break;
+ case SyntaxKind.LetClause:
+ if (!queryExpressionProcessingInfo.TryAdd(queryClause, ((LetClauseSyntax)queryClause).Identifier))
+ {
+ return false;
+ }
+
+ break;
+ case SyntaxKind.FromClause:
+ var fromClause = (FromClauseSyntax)queryClause;
+ if (!queryExpressionProcessingInfo.TryAdd(queryClause, fromClause.Identifier))
+ {
+ return false;
+ }
+
+ break;
+ case SyntaxKind.JoinClause:
+ var joinClause = (JoinClauseSyntax)queryClause;
+ if (joinClause.Into == null) // GroupJoin is not supported
+ {
+ if (queryExpressionProcessingInfo.TryAdd(joinClause, joinClause.Identifier))
+ {
+ break;
+ }
+ }
+
+ return false;
+ // OrderBy is not supported by foreach.
+ default:
+ return false;
+ }
+ }
+
+ // GroupClause is not supported by the conversion
+ if (!(queryBody.SelectOrGroup is SelectClauseSyntax selectClause))
+ {
+ return false;
+ }
+
+ if (_semanticModel.GetTypeInfo(selectClause.Expression, _cancellationToken).Type.ContainsAnonymousType())
+ {
+ return false;
+ }
+
+ queryExpressionProcessingInfo.Add(selectClause);
+ queryBody = queryBody.Continuation?.Body;
+ } while (queryBody != null);
+
+ return true;
+ }
+
+ private static BlockSyntax AddToBlockTop(StatementSyntax newStatement, StatementSyntax statement)
+ {
+ if (statement is BlockSyntax block)
+ {
+ return block.WithStatements(block.Statements.Insert(0, newStatement));
+ }
+ else
+ {
+ return SyntaxFactory.Block(newStatement, statement);
+ }
+ }
+
+ private bool IsLocalOrParameterSymbol(ExpressionSyntax expression)
+ => IsLocalOrParameterSymbol(_semanticModel.GetOperation(expression, _cancellationToken));
+
+ private static bool IsLocalOrParameterSymbol(IOperation operation)
+ {
+ if (operation is IConversionOperation conversion && conversion.IsImplicit)
+ {
+ return IsLocalOrParameterSymbol(conversion.Operand);
+ }
+
+ return operation.Kind == OperationKind.LocalReference || operation.Kind == OperationKind.ParameterReference;
+ }
+
+ private static BlockSyntax WrapWithBlock(StatementSyntax statement)
+ => statement is BlockSyntax block ? block : SyntaxFactory.Block(statement);
+
+ // Checks if the node is within an immediate lambda or within an immediate anonymous method.
+ // 'lambda => node' returns true
+ // 'lambda => localfunction => node' returns false
+ // 'member => node' returns false
+ private static bool IsWithinImmediateLambdaOrAnonymousMethod(SyntaxNode node)
+ {
+ while (node != null)
+ {
+ switch (node.Kind())
+ {
+ case SyntaxKind.AnonymousMethodExpression:
+ case SyntaxKind.ParenthesizedLambdaExpression:
+ case SyntaxKind.SimpleLambdaExpression:
+ return true;
+ case SyntaxKind.LocalFunctionStatement:
+ return false;
+ default:
+ if (node is MemberDeclarationSyntax)
+ {
+ return false;
+ }
+
+ break;
+ }
+
+ node = node.Parent;
+ }
+
+ return false;
+ }
+
+ private class QueryExpressionProcessingInfo
+ {
+ public Stack Stack { get; private set; }
+
+ public HashSet IdentifierNames { get; private set; }
+
+ public QueryExpressionProcessingInfo(FromClauseSyntax fromClause)
+ {
+ Stack = new Stack();
+ Stack.Push(fromClause);
+ IdentifierNames = new HashSet();
+ IdentifierNames.Add((fromClause.Identifier.ValueText));
+ }
+
+ public bool TryAdd(CSharpSyntaxNode node, SyntaxToken identifier)
+ {
+ // Duplicate identifiers are not allowed.
+ // var q = from x in new[] { 1 } select x + 2 into x where x > 0 select 7 into y let x = ""aaa"" select x;
+ if (!IdentifierNames.Add(identifier.ValueText))
+ {
+ return false;
+ }
+
+ Stack.Push(node);
+ return true;
+ }
+
+ public void Add(CSharpSyntaxNode node) => Stack.Push(node);
+
+ public bool ContainsIdentifier(SyntaxToken identifier)
+ => IdentifierNames.Contains(identifier.ValueText);
+ }
+ }
+ }
+}
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf
index 29bced5fd8c77..f8761d71a9d41 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf
@@ -592,11 +592,6 @@
Přidejte položku this.
-
-
- Převést if na switch
-
-
-
-
- Convert 'for' to 'foreach'
+
+
+ Convert to 'switch'
+
+
+
+
+ Convert to 'foreach'
+
+
+
+
+ Convert to 'for'