未验证 提交 4278a17b 编写于 作者: A Allison Chou 提交者: GitHub

Merge pull request #40284 from allisonchou/ExtractLocalFunctionNamingConvention

Fix extract local function bugs
......@@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.ExtractMethod
......@@ -21,6 +22,39 @@ protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspa
private int CodeActionIndex => 1;
private const string EditorConfigNaming_CamelCase = @"[*]
# Naming rules
dotnet_naming_rule.local_functions_should_be_camel_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_camel_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_camel_case.style = camel_case
# Symbol specifications
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
# Naming styles
dotnet_naming_style.camel_case.capitalization = camel_case";
private const string EditorConfigNaming_PascalCase = @"[*]
# Naming rules
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
dotnet_naming_symbols.local_functions.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.capitalization = pascal_case";
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestPartialSelection_StaticOptionTrue()
{
......@@ -1538,7 +1572,7 @@ static void NewMethod()
}", CodeActionIndex);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/39946"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
[Fact, WorkItem(39946, "https://github.com/dotnet/roslyn/issues/39946"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task ExtractLocalFunctionCall_3()
{
await TestInRegularAndScriptAsync(@"
......@@ -1551,7 +1585,6 @@ static void LocalParent()
[|void Local() { }
Local();|]
}
}
}", @"
class C
......@@ -1572,7 +1605,7 @@ static void NewMethod()
}", CodeActionIndex);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/39946"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
[Fact, WorkItem(39946, "https://github.com/dotnet/roslyn/issues/39946"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task ExtractFunctionUnderLocalFunctionCall()
{
await TestInRegularAndScriptAsync(@"
......@@ -3790,6 +3823,433 @@ static int NewMethod()
}", CodeActionIndex);
}
[Fact, WorkItem(40188, "https://github.com/dotnet/roslyn/issues/40188"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestEditorconfigSetting_StaticLocalFunction_True()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool test = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_prefer_static_local_function = true:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
{|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
static void NewMethod()
{
bool test = true;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_prefer_static_local_function = true:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40188, "https://github.com/dotnet/roslyn/issues/40188"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestEditorconfigSetting_StaticLocalFunction_False()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool test = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_prefer_static_local_function = false:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
{|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
void NewMethod()
{
bool test = true;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_prefer_static_local_function = false:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40188, "https://github.com/dotnet/roslyn/issues/40188"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestEditorconfigSetting_ExpressionBodiedLocalFunction_True()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_local_functions = true:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
static bool NewMethod() => true;
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_local_functions = true:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40188, "https://github.com/dotnet/roslyn/issues/40188"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestEditorconfigSetting_ExpressionBodiedLocalFunction_False()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_local_functions = false:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
static bool NewMethod()
{
return true;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_local_functions = false:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_CamelCase()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:newMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
static bool newMethod()
{
return true;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_CamelCase_GetName()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class MethodExtraction
{
void TestMethod()
{
int a = [|1 + 1|];
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class MethodExtraction
{
void TestMethod()
{
int a = {|Rename:getA|}();
static int getA()
{
return 1 + 1;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_PascalCase()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_PascalCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
static bool NewMethod()
{
return true;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_PascalCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_PascalCase_GetName()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class MethodExtraction
{
void TestMethod()
{
int a = [|1 + 1|];
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_PascalCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class MethodExtraction
{
void TestMethod()
{
int a = {|Rename:GetA|}();
static int GetA()
{
return 1 + 1;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_PascalCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_CamelCase_DoesntApply()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_CamelCase + @"
dotnet_naming_symbols.local_functions.required_modifiers = static
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
bool NewMethod()
{
return true;
}
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_CamelCase + @"
dotnet_naming_symbols.local_functions.required_modifiers = static
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected, CodeActionIndex, options: Option(CSharpCodeStyleOptions.PreferStaticLocalFunction, CodeStyleOptions.FalseWithSilentEnforcement));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestMissingInLocalFunctionDeclaration_ExpressionBody()
{
......
......@@ -17,6 +17,23 @@ public class ExtractMethodTests : AbstractCSharpCodeActionTest
protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
=> new ExtractMethodCodeRefactoringProvider();
private const string EditorConfigNaming_LocalFunctions_CamelCase = @"[*]
# Naming rules
dotnet_naming_rule.local_functions_should_be_camel_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_camel_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_camel_case.style = camel_case
# Symbol specifications
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
dotnet_naming_symbols.local_functions.required_modifiers =
# Naming styles
dotnet_naming_style.camel_case.capitalization = camel_case";
[Fact]
[WorkItem(39946, "https://github.com/dotnet/roslyn/issues/39946")]
public async Task LocalFuncExtract()
......@@ -3205,7 +3222,7 @@ private static bool NewMethod(bool b)
}", options: Option(CSharpCodeStyleOptions.PreferStaticLocalFunction, CodeStyleOptions.FalseWithSuggestionEnforcement));
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/39946"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)]
[Fact, WorkItem(39946, "https://github.com/dotnet/roslyn/issues/39946"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)]
public async Task ExtractLocalFunctionCallAndDeclaration()
{
await TestInRegularAndScriptAsync(@"
......@@ -3638,5 +3655,192 @@ private static async Task NewMethod(TimeSpan duration)
}
}");
}
[Fact, WorkItem(40188, "https://github.com/dotnet/roslyn/issues/40188"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)]
public async Task TestEditorconfigSetting_ExpressionBodiedLocalFunction_True()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_methods = true:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
}
private static bool NewMethod() => true;
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_methods = true:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected);
}
[Fact, WorkItem(40188, "https://github.com/dotnet/roslyn/issues/40188"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)]
public async Task TestEditorconfigSetting_ExpressionBodiedLocalFunction_False()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_methods = false:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
}
private static bool NewMethod()
{
return true;
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">[*.cs]
csharp_style_expression_bodied_methods = false:silent
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_CamelCase_VerifyLocalFunctionSettingsDontApply()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class Program1
{
static void Main()
{
[|bool b = true;|]
System.Console.WriteLine(b != true ? b = true : b = false);
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_LocalFunctions_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class Program1
{
static void Main()
{
bool b = {|Rename:NewMethod|}();
System.Console.WriteLine(b != true ? b = true : b = false);
}
private static bool NewMethod()
{
return true;
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_LocalFunctions_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected);
}
[Fact, WorkItem(40209, "https://github.com/dotnet/roslyn/issues/40209"), Trait(Traits.Feature, Traits.Features.CodeActionsExtractLocalFunction)]
public async Task TestNaming_CamelCase_VerifyLocalFunctionSettingsDontApply_GetName()
{
var input = @"
<Workspace>
<Project Language = ""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath = ""z:\\file.cs"">
class MethodExtraction
{
void TestMethod()
{
int a = [|1 + 1|];
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_LocalFunctions_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document FilePath=""z:\\file.cs"">
class MethodExtraction
{
void TestMethod()
{
int a = {|Rename:GetA|}();
}
private static int GetA()
{
return 1 + 1;
}
}
</Document>
<AnalyzerConfigDocument FilePath = ""z:\\.editorconfig"">" + EditorConfigNaming_LocalFunctions_CamelCase + @"
</AnalyzerConfigDocument>
</Project>
</Workspace>";
await TestInRegularAndScriptAsync(input, expected);
}
}
}
......@@ -115,9 +115,9 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
typeParameters: typeParameters.ToImmutableArray(),
parameters: parameters.AddRange(capturesAsParameters));
var defaultOptions = CodeGenerationOptions.Default;
var method = MethodGenerator.GenerateMethodDeclaration(methodSymbol, CodeGenerationDestination.Unspecified,
document.Project.Solution.Workspace, defaultOptions, root.SyntaxTree.Options);
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var defaultOptions = new CodeGenerationOptions(options: options);
var method = MethodGenerator.GenerateMethodDeclaration(methodSymbol, CodeGenerationDestination.Unspecified, defaultOptions, root.SyntaxTree.Options);
var generator = s_generator;
var editor = new SyntaxEditor(root, generator);
......
......@@ -6,9 +6,14 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification;
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod
{
......@@ -22,8 +27,9 @@ private class ExpressionCodeGenerator : CSharpCodeGenerator
InsertionPoint insertionPoint,
SelectionResult selectionResult,
AnalyzerResult analyzerResult,
OptionSet options,
bool localFunction)
: base(insertionPoint, selectionResult, analyzerResult, localFunction)
: base(insertionPoint, selectionResult, analyzerResult, options, localFunction)
{
}
......@@ -32,9 +38,10 @@ public static bool IsExtractMethodOnExpression(SelectionResult code)
return code.SelectionInExpression;
}
protected override SyntaxToken CreateMethodName(bool localFunction)
protected override SyntaxToken CreateMethodName()
{
var methodName = "NewMethod";
var methodName = GenerateMethodNameFromUserPreference();
var containingScope = this.CSharpSelectionResult.GetContainingScope();
methodName = GetMethodNameBasedOnExpression(methodName, containingScope);
......@@ -44,7 +51,7 @@ protected override SyntaxToken CreateMethodName(bool localFunction)
return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, methodName));
}
private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNode expression)
private string GetMethodNameBasedOnExpression(string methodName, SyntaxNode expression)
{
if (expression.Parent != null &&
expression.Parent.Kind() == SyntaxKind.EqualsValueClause &&
......@@ -52,7 +59,7 @@ private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNo
expression.Parent.Parent.Kind() == SyntaxKind.VariableDeclarator)
{
var name = ((VariableDeclaratorSyntax)expression.Parent.Parent).Identifier.ValueText;
return (name != null && name.Length > 0) ? MakeMethodName("Get", name) : methodName;
return (name != null && name.Length > 0) ? MakeMethodName("Get", name, methodName.Equals(NewMethodCamelCaseStr)) : methodName;
}
if (expression is MemberAccessExpressionSyntax memberAccess)
......@@ -81,7 +88,8 @@ private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNo
}
var unqualifiedNameIdentifierValueText = unqualifiedName.Identifier.ValueText;
return (unqualifiedNameIdentifierValueText != null && unqualifiedNameIdentifierValueText.Length > 0) ? MakeMethodName("Get", unqualifiedNameIdentifierValueText) : methodName;
return (unqualifiedNameIdentifierValueText != null && unqualifiedNameIdentifierValueText.Length > 0) ?
MakeMethodName("Get", unqualifiedNameIdentifierValueText, methodName.Equals(NewMethodCamelCaseStr)) : methodName;
}
return methodName;
......
......@@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Options;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod
......@@ -19,8 +20,9 @@ public class MultipleStatementsCodeGenerator : CSharpCodeGenerator
InsertionPoint insertionPoint,
SelectionResult selectionResult,
AnalyzerResult analyzerResult,
OptionSet options,
bool localFunction)
: base(insertionPoint, selectionResult, analyzerResult, localFunction)
: base(insertionPoint, selectionResult, analyzerResult, options, localFunction)
{
}
......@@ -41,7 +43,7 @@ public static bool IsExtractMethodOnMultipleStatements(SelectionResult code)
return false;
}
protected override SyntaxToken CreateMethodName(bool localFunction) => CreateMethodNameForStatementGenerators(localFunction);
protected override SyntaxToken CreateMethodName() => GenerateMethodNameForStatementGenerators();
protected override IEnumerable<StatementSyntax> GetInitialStatementsForMethodDefinitions()
{
......
......@@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Options;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod
......@@ -19,8 +20,9 @@ public class SingleStatementCodeGenerator : CSharpCodeGenerator
InsertionPoint insertionPoint,
SelectionResult selectionResult,
AnalyzerResult analyzerResult,
OptionSet options,
bool localFunction)
: base(insertionPoint, selectionResult, analyzerResult, localFunction)
: base(insertionPoint, selectionResult, analyzerResult, options, localFunction)
{
}
......@@ -33,7 +35,7 @@ public static bool IsExtractMethodOnSingleStatement(SelectionResult code)
return firstStatement == lastStatement || firstStatement.Span.Contains(lastStatement.Span);
}
protected override SyntaxToken CreateMethodName(bool localFunction) => CreateMethodNameForStatementGenerators(localFunction);
protected override SyntaxToken CreateMethodName() => GenerateMethodNameForStatementGenerators();
protected override IEnumerable<StatementSyntax> GetInitialStatementsForMethodDefinitions()
{
......
......@@ -9,15 +9,19 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification;
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod
{
......@@ -27,14 +31,18 @@ private abstract partial class CSharpCodeGenerator : CodeGenerator<StatementSynt
{
private readonly SyntaxToken _methodName;
private const string NewMethodPascalCaseStr = "NewMethod";
private const string NewMethodCamelCaseStr = "newMethod";
public static Task<GeneratedCode> GenerateAsync(
InsertionPoint insertionPoint,
SelectionResult selectionResult,
AnalyzerResult analyzerResult,
OptionSet options,
bool localFunction,
CancellationToken cancellationToken)
{
var codeGenerator = Create(insertionPoint, selectionResult, analyzerResult, localFunction);
var codeGenerator = Create(insertionPoint, selectionResult, analyzerResult, options, localFunction);
return codeGenerator.GenerateAsync(cancellationToken);
}
......@@ -42,21 +50,22 @@ private abstract partial class CSharpCodeGenerator : CodeGenerator<StatementSynt
InsertionPoint insertionPoint,
SelectionResult selectionResult,
AnalyzerResult analyzerResult,
OptionSet options,
bool localFunction)
{
if (ExpressionCodeGenerator.IsExtractMethodOnExpression(selectionResult))
{
return new ExpressionCodeGenerator(insertionPoint, selectionResult, analyzerResult, localFunction);
return new ExpressionCodeGenerator(insertionPoint, selectionResult, analyzerResult, options, localFunction);
}
if (SingleStatementCodeGenerator.IsExtractMethodOnSingleStatement(selectionResult))
{
return new SingleStatementCodeGenerator(insertionPoint, selectionResult, analyzerResult, localFunction);
return new SingleStatementCodeGenerator(insertionPoint, selectionResult, analyzerResult, options, localFunction);
}
if (MultipleStatementsCodeGenerator.IsExtractMethodOnMultipleStatements(selectionResult))
{
return new MultipleStatementsCodeGenerator(insertionPoint, selectionResult, analyzerResult, localFunction);
return new MultipleStatementsCodeGenerator(insertionPoint, selectionResult, analyzerResult, options, localFunction);
}
return Contract.FailWithReturn<CSharpCodeGenerator>("Unknown selection");
......@@ -66,12 +75,13 @@ private abstract partial class CSharpCodeGenerator : CodeGenerator<StatementSynt
InsertionPoint insertionPoint,
SelectionResult selectionResult,
AnalyzerResult analyzerResult,
OptionSet options,
bool localFunction)
: base(insertionPoint, selectionResult, analyzerResult, localFunction)
: base(insertionPoint, selectionResult, analyzerResult, options, localFunction)
{
Contract.ThrowIfFalse(this.SemanticDocument == selectionResult.SemanticDocument);
var nameToken = CreateMethodName(localFunction);
var nameToken = CreateMethodName();
_methodName = nameToken.WithAdditionalAnnotations(this.MethodNameAnnotation);
}
......@@ -210,6 +220,14 @@ private DeclarationModifiers CreateMethodModifiers()
var isStatic = !this.AnalyzerResult.UseInstanceMember;
var isReadOnly = this.AnalyzerResult.ShouldBeReadOnly;
// Static local functions are only supported in C# 8.0 and later
var languageVersion = ((CSharpParseOptions)this.SemanticDocument.SyntaxTree.Options).LanguageVersion;
if (LocalFunction && (!this.Options.GetOption(CSharpCodeStyleOptions.PreferStaticLocalFunction).Value || languageVersion < LanguageVersion.CSharp8))
{
isStatic = false;
}
return new DeclarationModifiers(
isUnsafe: isUnsafe,
isAsync: isAsync,
......@@ -777,19 +795,45 @@ static bool ReturnOperationBelongsToMethod(SyntaxNode returnOperationSyntax, Syn
}
}
protected SyntaxToken CreateMethodNameForStatementGenerators(bool localFunction)
protected SyntaxToken GenerateMethodNameForStatementGenerators()
{
var semanticModel = this.SemanticDocument.SemanticModel;
var nameGenerator = new UniqueNameGenerator(semanticModel);
var scope = this.CSharpSelectionResult.GetContainingScope();
// If extracting a local function, we want to ensure all local variables are considered when generating a unique name.
if (localFunction)
if (LocalFunction)
{
scope = this.CSharpSelectionResult.GetFirstTokenInSelection().Parent;
}
return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(scope, "NewMethod"));
return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(scope, GenerateMethodNameFromUserPreference()));
}
protected string GenerateMethodNameFromUserPreference()
{
var methodName = NewMethodPascalCaseStr;
if (!LocalFunction)
{
return methodName;
}
// For local functions, pascal case and camel case should be the most common and therefore we only consider those cases.
var namingPreferences = this.Options.GetOption(SimplificationOptions.NamingPreferences, LanguageNames.CSharp);
var localFunctionPreferences = namingPreferences.SymbolSpecifications.Where(symbol => symbol.AppliesTo(new SymbolKindOrTypeKind(MethodKind.LocalFunction), CreateMethodModifiers(), null));
var namingRules = namingPreferences.Rules.NamingRules;
var localFunctionKind = new SymbolKindOrTypeKind(MethodKind.LocalFunction);
if (LocalFunction)
{
if (namingRules.Any(rule => rule.NamingStyle.CapitalizationScheme.Equals(Capitalization.CamelCase) && rule.SymbolSpecification.AppliesTo(localFunctionKind, CreateMethodModifiers(), null)))
{
methodName = NewMethodCamelCaseStr;
}
}
// We default to pascal case.
return methodName;
}
}
}
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
......@@ -87,9 +88,9 @@ protected override async Task<SemanticDocument> ExpandAsync(SelectionResult sele
return await selection.SemanticDocument.WithSyntaxRootAsync(selection.SemanticDocument.Root.ReplaceNode(lastExpression, newExpression), cancellationToken).ConfigureAwait(false);
}
protected override Task<GeneratedCode> GenerateCodeAsync(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzeResult, CancellationToken cancellationToken)
protected override Task<GeneratedCode> GenerateCodeAsync(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzeResult, OptionSet options, CancellationToken cancellationToken)
{
return CSharpCodeGenerator.GenerateAsync(insertionPoint, selectionResult, analyzeResult, LocalFunction, cancellationToken);
return CSharpCodeGenerator.GenerateAsync(insertionPoint, selectionResult, analyzeResult, options, LocalFunction, cancellationToken);
}
protected override IEnumerable<AbstractFormattingRule> GetFormattingRules(Document document)
......
......@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
......@@ -31,9 +32,10 @@ protected abstract partial class CodeGenerator<TStatement, TExpression, TNodeUnd
protected readonly SelectionResult SelectionResult;
protected readonly AnalyzerResult AnalyzerResult;
protected readonly OptionSet Options;
protected readonly bool LocalFunction;
protected CodeGenerator(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzerResult, bool localFunction = false)
protected CodeGenerator(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzerResult, OptionSet options = null, bool localFunction = false)
{
Contract.ThrowIfFalse(insertionPoint.SemanticDocument == analyzerResult.SemanticDocument);
......@@ -43,6 +45,7 @@ protected CodeGenerator(InsertionPoint insertionPoint, SelectionResult selection
SelectionResult = selectionResult;
AnalyzerResult = analyzerResult;
Options = options;
LocalFunction = localFunction;
MethodNameAnnotation = new SyntaxAnnotation();
......@@ -58,7 +61,7 @@ protected CodeGenerator(InsertionPoint insertionPoint, SelectionResult selection
protected abstract OperationStatus<IMethodSymbol> GenerateMethodDefinition(bool localFunction, CancellationToken cancellationToken);
protected abstract SyntaxToken CreateIdentifier(string name);
protected abstract SyntaxToken CreateMethodName(bool localFunction);
protected abstract SyntaxToken CreateMethodName();
protected abstract bool LastStatementOrHasReturnStatementInReturnableConstruct();
protected abstract TNodeUnderContainer GetFirstStatementOrInitializerSelectedAtCallSite();
......@@ -91,7 +94,7 @@ public async Task<GeneratedCode> GenerateAsync(CancellationToken cancellationTok
destination = InsertionPoint.With(callSiteDocument).GetContext();
var localMethod = codeGenerationService.CreateMethodDeclaration(
method: result.Data,
options: new CodeGenerationOptions(generateDefaultAccessibility: false, generateMethodBodies: true, parseOptions: destination?.SyntaxTree.Options));
options: new CodeGenerationOptions(generateDefaultAccessibility: false, generateMethodBodies: true, options: this.Options, parseOptions: destination?.SyntaxTree.Options));
newContainer = codeGenerationService.AddStatements(destination, new[] { localMethod }, cancellationToken: cancellationToken);
}
else
......@@ -102,7 +105,7 @@ public async Task<GeneratedCode> GenerateAsync(CancellationToken cancellationTok
destination = previousMemberNode.Parent ?? previousMemberNode;
newContainer = codeGenerationService.AddMethod(
destination, result.Data,
new CodeGenerationOptions(afterThisLocation: previousMemberNode.GetLocation(), generateDefaultAccessibility: true, generateMethodBodies: true),
new CodeGenerationOptions(afterThisLocation: previousMemberNode.GetLocation(), generateDefaultAccessibility: true, generateMethodBodies: true, options: this.Options),
cancellationToken);
}
......
......@@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Options;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.ExtractMethod
......@@ -28,7 +29,7 @@ public MethodExtractor(SelectionResult selectionResult, bool localFunction)
protected abstract Task<TriviaResult> PreserveTriviaAsync(SelectionResult selectionResult, CancellationToken cancellationToken);
protected abstract Task<SemanticDocument> ExpandAsync(SelectionResult selection, CancellationToken cancellationToken);
protected abstract Task<GeneratedCode> GenerateCodeAsync(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzeResult, CancellationToken cancellationToken);
protected abstract Task<GeneratedCode> GenerateCodeAsync(InsertionPoint insertionPoint, SelectionResult selectionResult, AnalyzerResult analyzeResult, OptionSet options, CancellationToken cancellationToken);
protected abstract SyntaxToken GetMethodNameAtInvocation(IEnumerable<SyntaxNodeOrToken> methodNames);
protected abstract IEnumerable<AbstractFormattingRule> GetFormattingRules(Document document);
......@@ -57,11 +58,13 @@ public async Task<ExtractMethodResult> ExtractMethodAsync(CancellationToken canc
cancellationToken.ThrowIfCancellationRequested();
var expandedDocument = await ExpandAsync(OriginalSelectionResult.With(triviaResult.SemanticDocument), cancellationToken).ConfigureAwait(false);
var options = await analyzeResult.SemanticDocument.Document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var generatedCode = await GenerateCodeAsync(
insertionPoint.With(expandedDocument),
OriginalSelectionResult.With(expandedDocument),
analyzeResult.With(expandedDocument),
options,
cancellationToken).ConfigureAwait(false);
var applied = await triviaResult.ApplyAsync(generatedCode, cancellationToken).ConfigureAwait(false);
......@@ -173,11 +176,16 @@ public async Task<ExtractMethodResult> ExtractMethodAsync(CancellationToken canc
return Tuple.Create(true, status);
}
internal static string MakeMethodName(string prefix, string originalName)
internal static string MakeMethodName(string prefix, string originalName, bool camelCase)
{
var startingWithLetter = originalName.ToCharArray().SkipWhile(c => !char.IsLetter(c)).ToArray();
var name = startingWithLetter.Length == 0 ? originalName : new string(startingWithLetter);
if (camelCase && !prefix.IsEmpty())
{
prefix = char.ToLowerInvariant(prefix[0]) + prefix.Substring(1);
}
return char.IsUpper(name[0]) ?
prefix + name :
prefix + char.ToUpper(name[0]).ToString() + name.Substring(1);
......
......@@ -3,6 +3,7 @@
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.ExtractMethod
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
......@@ -20,7 +21,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
Return code.SelectionInExpression
End Function
Protected Overrides Function CreateMethodName(localFunction As Boolean) As SyntaxToken
Protected Overrides Function CreateMethodName() As SyntaxToken
Dim methodName = "NewMethod"
Dim containingScope = CType(VBSelectionResult.GetContainingScope(), SyntaxNode)
......@@ -47,7 +48,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
End If
Dim name = identifierNode.Identifier.ValueText
Return If(name IsNot Nothing AndAlso name.Length > 0, MakeMethodName("Get", name), methodName)
Return If(name IsNot Nothing AndAlso name.Length > 0, MakeMethodName("Get", name, camelCase:=False), methodName)
End If
If TypeOf expression Is MemberAccessExpressionSyntax Then
......@@ -57,7 +58,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
If TypeOf expression Is NameSyntax Then
Dim lastDottedName = CType(expression, NameSyntax).GetLastDottedName()
Dim plainName = CType(lastDottedName, SimpleNameSyntax).Identifier.ValueText
Return If(plainName IsNot Nothing AndAlso plainName.Length > 0, MakeMethodName("Get", plainName), methodName)
Return If(plainName IsNot Nothing AndAlso plainName.Length > 0, MakeMethodName("Get", plainName, camelCase:=False), methodName)
End If
Return methodName
......
......@@ -3,6 +3,7 @@
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.ExtractMethod
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
......@@ -29,7 +30,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
Return False
End Function
Protected Overrides Function CreateMethodName(localFunction As Boolean) As SyntaxToken
Protected Overrides Function CreateMethodName() As SyntaxToken
' change this to more smarter one.
Dim semanticModel = SemanticDocument.SemanticModel
Dim nameGenerator = New UniqueNameGenerator(semanticModel)
......
......@@ -3,6 +3,7 @@
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.ExtractMethod
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
......@@ -23,7 +24,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
Return firstStatement Is lastStatement OrElse firstStatement.Span.Contains(lastStatement.Span)
End Function
Protected Overrides Function CreateMethodName(localFunction As Boolean) As SyntaxToken
Protected Overrides Function CreateMethodName() As SyntaxToken
' change this to more smarter one.
Dim semanticModel = CType(SemanticDocument.SemanticModel, SemanticModel)
Dim nameGenerator = New UniqueNameGenerator(semanticModel)
......
......@@ -45,7 +45,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
MyBase.New(insertionPoint, selectionResult, analyzerResult)
Contract.ThrowIfFalse(Me.SemanticDocument Is selectionResult.SemanticDocument)
Me._methodName = CreateMethodName(localFunction:=False).WithAdditionalAnnotations(MethodNameAnnotation)
Me._methodName = CreateMethodName().WithAdditionalAnnotations(MethodNameAnnotation)
End Sub
Private ReadOnly Property VBSelectionResult() As VisualBasicSelectionResult
......
......@@ -63,7 +63,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
Return Await selection.SemanticDocument.WithSyntaxRootAsync(selection.SemanticDocument.Root.ReplaceNode(lastExpression, newStatement), cancellationToken).ConfigureAwait(False)
End Function
Protected Overrides Function GenerateCodeAsync(insertionPoint As InsertionPoint, selectionResult As SelectionResult, analyzeResult As AnalyzerResult, cancellationToken As CancellationToken) As Task(Of GeneratedCode)
Protected Overrides Function GenerateCodeAsync(insertionPoint As InsertionPoint, selectionResult As SelectionResult, analyzeResult As AnalyzerResult, options As OptionSet, cancellationToken As CancellationToken) As Task(Of GeneratedCode)
Return VisualBasicCodeGenerator.GenerateResultAsync(insertionPoint, selectionResult, analyzeResult, cancellationToken)
End Function
......
......@@ -113,6 +113,8 @@ protected override TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode
{
CheckDeclarationNode<TypeDeclarationSyntax, CompilationUnitSyntax, NamespaceDeclarationSyntax>(destination);
options = options.With(options: options.Options ?? Workspace.Options);
// Synthesized methods for properties/events are not things we actually generate
// declarations for.
if (method.AssociatedSymbol is IEventSymbol)
......@@ -155,7 +157,7 @@ protected override TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode
}
return Cast<TDeclarationNode>(MethodGenerator.AddMethodTo(
typeDeclaration, method, Workspace, options, availableIndices));
typeDeclaration, method, options, availableIndices));
}
if (method.IsConstructor() ||
......@@ -167,12 +169,12 @@ protected override TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode
if (destination is CompilationUnitSyntax compilationUnit)
{
return Cast<TDeclarationNode>(
MethodGenerator.AddMethodTo(compilationUnit, method, Workspace, options, availableIndices));
MethodGenerator.AddMethodTo(compilationUnit, method, options, availableIndices));
}
var ns = Cast<NamespaceDeclarationSyntax>(destination);
return Cast<TDeclarationNode>(
MethodGenerator.AddMethodTo(ns, method, Workspace, options, availableIndices));
MethodGenerator.AddMethodTo(ns, method, options, availableIndices));
}
protected override TDeclarationNode AddProperty<TDeclarationNode>(TDeclarationNode destination, IPropertySymbol property, CodeGenerationOptions options, IList<bool> availableIndices)
......@@ -573,15 +575,18 @@ public override SyntaxNode CreateFieldDeclaration(IFieldSymbol field, CodeGenera
}
}
if (method.IsDestructor())
{
return DestructorGenerator.GenerateDestructorDeclaration(method, destination, options);
}
options = options.With(options: options.Options ?? Workspace.Options);
if (method.IsConstructor())
{
return ConstructorGenerator.GenerateConstructorDeclaration(
method, destination, Workspace, options, options.ParseOptions);
}
else if (method.IsDestructor())
{
return DestructorGenerator.GenerateDestructorDeclaration(method, destination, options);
}
else if (method.IsUserDefinedOperator())
{
return OperatorGenerator.GenerateOperatorDeclaration(
......@@ -594,13 +599,13 @@ public override SyntaxNode CreateFieldDeclaration(IFieldSymbol field, CodeGenera
}
else if (method.IsLocalFunction())
{
return MethodGenerator.GenerateLocalMethodDeclaration(
method, destination, Workspace, options, options.ParseOptions);
return MethodGenerator.GenerateLocalFunctionDeclaration(
method, destination, options, options.ParseOptions);
}
else
{
return MethodGenerator.GenerateMethodDeclaration(
method, destination, Workspace, options, options.ParseOptions);
method, destination, options, options.ParseOptions);
}
}
......
......@@ -18,12 +18,11 @@ internal static class MethodGenerator
internal static NamespaceDeclarationSyntax AddMethodTo(
NamespaceDeclarationSyntax destination,
IMethodSymbol method,
Workspace workspace,
CodeGenerationOptions options,
IList<bool> availableIndices)
{
var declaration = GenerateMethodDeclaration(
method, CodeGenerationDestination.Namespace, workspace, options,
method, CodeGenerationDestination.Namespace, options,
destination?.SyntaxTree.Options ?? options.ParseOptions);
var members = Insert(destination.Members, declaration, options, availableIndices, after: LastMethod);
return destination.WithMembers(members.ToSyntaxList());
......@@ -32,12 +31,11 @@ internal static class MethodGenerator
internal static CompilationUnitSyntax AddMethodTo(
CompilationUnitSyntax destination,
IMethodSymbol method,
Workspace workspace,
CodeGenerationOptions options,
IList<bool> availableIndices)
{
var declaration = GenerateMethodDeclaration(
method, CodeGenerationDestination.CompilationUnit, workspace, options,
method, CodeGenerationDestination.CompilationUnit, options,
destination?.SyntaxTree.Options ?? options.ParseOptions);
var members = Insert(destination.Members, declaration, options, availableIndices, after: LastMethod);
return destination.WithMembers(members.ToSyntaxList());
......@@ -46,12 +44,11 @@ internal static class MethodGenerator
internal static TypeDeclarationSyntax AddMethodTo(
TypeDeclarationSyntax destination,
IMethodSymbol method,
Workspace workspace,
CodeGenerationOptions options,
IList<bool> availableIndices)
{
var methodDeclaration = GenerateMethodDeclaration(
method, GetDestination(destination), workspace, options,
method, GetDestination(destination), options,
destination?.SyntaxTree.Options ?? options.ParseOptions);
// Create a clone of the original type with the new method inserted.
......@@ -62,7 +59,7 @@ internal static class MethodGenerator
public static MethodDeclarationSyntax GenerateMethodDeclaration(
IMethodSymbol method, CodeGenerationDestination destination,
Workspace workspace, CodeGenerationOptions options,
CodeGenerationOptions options,
ParseOptions parseOptions)
{
options ??= CodeGenerationOptions.Default;
......@@ -74,15 +71,15 @@ internal static class MethodGenerator
}
var declaration = GenerateMethodDeclarationWorker(
method, destination, workspace, options, parseOptions);
method, destination, options, parseOptions);
return AddAnnotationsTo(method,
ConditionallyAddDocumentationCommentTo(declaration, method, options));
}
public static LocalFunctionStatementSyntax GenerateLocalMethodDeclaration(
public static LocalFunctionStatementSyntax GenerateLocalFunctionDeclaration(
IMethodSymbol method, CodeGenerationDestination destination,
Workspace workspace, CodeGenerationOptions options,
CodeGenerationOptions options,
ParseOptions parseOptions)
{
options ??= CodeGenerationOptions.Default;
......@@ -93,8 +90,8 @@ internal static class MethodGenerator
return reusableSyntax;
}
var declaration = GenerateLocalMethodDeclarationWorker(
method, destination, workspace, options, parseOptions);
var declaration = GenerateLocalFunctionDeclarationWorker(
method, destination, options, parseOptions);
return AddAnnotationsTo(method,
ConditionallyAddDocumentationCommentTo(declaration, method, options));
......@@ -102,7 +99,7 @@ internal static class MethodGenerator
private static MethodDeclarationSyntax GenerateMethodDeclarationWorker(
IMethodSymbol method, CodeGenerationDestination destination,
Workspace workspace, CodeGenerationOptions options, ParseOptions parseOptions)
CodeGenerationOptions options, ParseOptions parseOptions)
{
// Don't rely on destination to decide if method body should be generated.
// Users of this service need to express their intention explicitly, either by
......@@ -114,7 +111,7 @@ internal static class MethodGenerator
var methodDeclaration = SyntaxFactory.MethodDeclaration(
attributeLists: GenerateAttributes(method, options, explicitInterfaceSpecifier != null),
modifiers: GenerateModifiers(method, destination, workspace, options, localFunction: false),
modifiers: GenerateModifiers(method, destination, options),
returnType: method.GenerateReturnTypeSyntax(),
explicitInterfaceSpecifier: explicitInterfaceSpecifier,
identifier: method.Name.ToIdentifierToken(),
......@@ -125,16 +122,16 @@ internal static class MethodGenerator
expressionBody: default,
semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default);
methodDeclaration = UseExpressionBodyIfDesired(workspace, methodDeclaration, parseOptions);
methodDeclaration = UseExpressionBodyIfDesired(options, methodDeclaration, parseOptions);
return AddFormatterAndCodeGeneratorAnnotationsTo(methodDeclaration);
}
private static LocalFunctionStatementSyntax GenerateLocalMethodDeclarationWorker(
private static LocalFunctionStatementSyntax GenerateLocalFunctionDeclarationWorker(
IMethodSymbol method, CodeGenerationDestination destination,
Workspace workspace, CodeGenerationOptions options, ParseOptions parseOptions)
CodeGenerationOptions options, ParseOptions parseOptions)
{
var localMethodDeclaration = SyntaxFactory.LocalFunctionStatement(
modifiers: GenerateModifiers(method, destination, workspace, options, localFunction: true, ((CSharpParseOptions)parseOptions).LanguageVersion),
var localFunctionDeclaration = SyntaxFactory.LocalFunctionStatement(
modifiers: GenerateModifiers(method, destination, options),
returnType: method.GenerateReturnTypeSyntax(),
identifier: method.Name.ToIdentifierToken(),
typeParameterList: GenerateTypeParameterList(method, options),
......@@ -144,18 +141,18 @@ internal static class MethodGenerator
expressionBody: default,
semicolonToken: default);
localMethodDeclaration = UseExpressionBodyIfDesired(workspace, localMethodDeclaration, parseOptions);
return AddFormatterAndCodeGeneratorAnnotationsTo(localMethodDeclaration);
localFunctionDeclaration = UseExpressionBodyIfDesired(options, localFunctionDeclaration, parseOptions);
return AddFormatterAndCodeGeneratorAnnotationsTo(localFunctionDeclaration);
}
private static MethodDeclarationSyntax UseExpressionBodyIfDesired(
Workspace workspace, MethodDeclarationSyntax methodDeclaration, ParseOptions options)
CodeGenerationOptions options, MethodDeclarationSyntax methodDeclaration, ParseOptions parseOptions)
{
if (methodDeclaration.ExpressionBody == null)
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods).Value;
var expressionBodyPreference = options.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods).Value;
if (methodDeclaration.Body.TryConvertToArrowExpressionBody(
methodDeclaration.Kind(), options, expressionBodyPreference,
methodDeclaration.Kind(), parseOptions, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
return methodDeclaration.WithBody(null)
......@@ -168,22 +165,22 @@ internal static class MethodGenerator
}
private static LocalFunctionStatementSyntax UseExpressionBodyIfDesired(
Workspace workspace, LocalFunctionStatementSyntax localMethodDeclaration, ParseOptions options)
CodeGenerationOptions options, LocalFunctionStatementSyntax localFunctionDeclaration, ParseOptions parseOptions)
{
if (localMethodDeclaration.ExpressionBody == null)
if (localFunctionDeclaration.ExpressionBody == null)
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedLocalFunctions).Value;
if (localMethodDeclaration.Body.TryConvertToArrowExpressionBody(
localMethodDeclaration.Kind(), options, expressionBodyPreference,
var expressionBodyPreference = options.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedLocalFunctions).Value;
if (localFunctionDeclaration.Body.TryConvertToArrowExpressionBody(
localFunctionDeclaration.Kind(), parseOptions, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
return localMethodDeclaration.WithBody(null)
return localFunctionDeclaration.WithBody(null)
.WithExpressionBody(expressionBody)
.WithSemicolonToken(semicolonToken);
}
}
return localMethodDeclaration;
return localFunctionDeclaration;
}
private static SyntaxList<AttributeListSyntax> GenerateAttributes(
......@@ -215,7 +212,7 @@ internal static class MethodGenerator
}
private static SyntaxTokenList GenerateModifiers(
IMethodSymbol method, CodeGenerationDestination destination, Workspace workspace, CodeGenerationOptions options, bool localFunction, LanguageVersion languageVersion = LanguageVersion.Latest)
IMethodSymbol method, CodeGenerationDestination destination, CodeGenerationOptions options)
{
var tokens = ArrayBuilder<SyntaxToken>.GetInstance();
......@@ -248,20 +245,7 @@ internal static class MethodGenerator
if (method.IsStatic)
{
if (localFunction)
{
var preferStaticLocalFunction = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferStaticLocalFunction).Value;
// Static local functions are only supported in C# 8.0 and later.
if (preferStaticLocalFunction && languageVersion >= LanguageVersion.CSharp8)
{
tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
}
}
else
{
tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
}
tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
}
// Don't show the readonly modifier if the containing type is already readonly
......
......@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
......@@ -132,6 +133,8 @@ internal class CodeGenerationOptions
/// </summary>
public bool ReuseSyntax { get; }
public OptionSet Options { get; }
public ParseOptions ParseOptions { get; }
public CodeGenerationOptions(
......@@ -150,6 +153,7 @@ internal class CodeGenerationOptions
bool autoInsertionLocation = true,
bool sortMembers = true,
bool reuseSyntax = false,
OptionSet options = null,
ParseOptions parseOptions = null)
{
CheckLocation(contextLocation, nameof(contextLocation));
......@@ -172,6 +176,7 @@ internal class CodeGenerationOptions
this.SortMembers = sortMembers;
this.ReuseSyntax = reuseSyntax;
this.Options = options;
this.ParseOptions = parseOptions ?? this.BestLocation?.SourceTree.Options;
}
......@@ -211,6 +216,7 @@ internal Location BestLocation
Optional<bool> autoInsertionLocation = default,
Optional<bool> sortMembers = default,
Optional<bool> reuseSyntax = default,
Optional<OptionSet> options = default,
Optional<ParseOptions> parseOptions = default)
{
var newContextLocation = contextLocation.HasValue ? contextLocation.Value : this.ContextLocation;
......@@ -228,6 +234,7 @@ internal Location BestLocation
var newAutoInsertionLocation = autoInsertionLocation.HasValue ? autoInsertionLocation.Value : this.AutoInsertionLocation;
var newSortMembers = sortMembers.HasValue ? sortMembers.Value : this.SortMembers;
var newReuseSyntax = reuseSyntax.HasValue ? reuseSyntax.Value : this.ReuseSyntax;
var newOptions = options.HasValue ? options.Value : this.Options;
var newParseOptions = parseOptions.HasValue ? parseOptions.Value : this.ParseOptions;
return new CodeGenerationOptions(
......@@ -246,6 +253,7 @@ internal Location BestLocation
newAutoInsertionLocation,
newSortMembers,
newReuseSyntax,
newOptions,
newParseOptions);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册