diff --git a/src/EditorFeatures/CSharpTest/MakeFieldReadonly/MakeFieldReadonlyTests.cs b/src/EditorFeatures/CSharpTest/MakeFieldReadonly/MakeFieldReadonlyTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..f7a871c01ea777e185e3bcd2a9ad34ecf0c45bfd --- /dev/null +++ b/src/EditorFeatures/CSharpTest/MakeFieldReadonly/MakeFieldReadonlyTests.cs @@ -0,0 +1,783 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.MakeFieldReadonly; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MakeFieldReadonly +{ + public class MakeFieldReadonlyTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + { + internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) + => (new CSharpMakeFieldReadonlyDiagnosticAnalyzer(), new CSharpMakeFieldReadonlyCodeFixProvider()); + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected")] + [InlineData("protected internal")] + [InlineData("private protected")] + public async Task NonPrivateField(string accessibility) + { + await TestMissingInRegularAndScriptAsync( +$@"class MyClass +{{ + {accessibility} int[| _goo |]; +}}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldIsEvent() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private event System.EventHandler [|Goo|]; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldIsReadonly() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private readonly int [|_goo|]; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldIsConst() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private const int [|_goo|]; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldNotAssigned() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; +}", +@"class MyClass +{ + private readonly int _goo; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldNotAssigned_Struct() + { + await TestInRegularAndScriptAsync( +@"struct MyStruct +{ + private int [|_goo|]; +}", +@"struct MyStruct +{ + private readonly int _goo; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInline() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|] = 0; +}", +@"class MyClass +{ + private readonly int _goo = 0; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task MultipleFieldsAssignedInline_AllCanBeReadonly() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|] = 0, _bar = 0; +}", +@"class MyClass +{ + private readonly int _goo = 0; + private int _bar = 0; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task ThreeFieldsAssignedInline_AllCanBeReadonly_SeparatesAllAndKeepsThemInOrder() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int _goo = 0, [|_bar|] = 0, _fizz = 0; +}", +@"class MyClass +{ + private int _goo = 0; + private readonly int _bar = 0; + private int _fizz = 0; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task MultipleFieldsAssignedInline_OneIsAssignedInMethod() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int _goo = 0, [|_bar|] = 0; + Goo() + { + _goo = 0; + } +}", +@"class MyClass +{ + private int _goo = 0; + private readonly int _bar = 0; + + Goo() + { + _goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task MultipleFieldsAssignedInline_NoInitializer() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|], _bar = 0; +}", +@"class MyClass +{ + private readonly int _goo; + private int _bar = 0; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInCtor() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + MyClass() + { + _goo = 0; + } +}", +@"class MyClass +{ + private readonly int _goo; + MyClass() + { + _goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInSimpleLambdaInCtor() + { + await TestMissingInRegularAndScriptAsync( +@"public class MyClass +{ + private int [|_goo|]; + public MyClass() + { + this.E = x => this._goo = 0; + } + + public Action E; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInLambdaInCtor() + { + await TestMissingInRegularAndScriptAsync( +@"public class MyClass +{ + private int [|_goo|]; + public MyClass() + { + this.E += (_, __) => this._goo = 0; + } + + public event EventHandler E; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInLambdaWithBlockInCtor() + { + await TestMissingInRegularAndScriptAsync( +@"public class MyClass +{ + private int [|_goo|]; + public MyClass() + { + this.E += (_, __) => { this._goo = 0; } + } + + public event EventHandler E; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInAnonymousFunctionInCtor() + { + await TestMissingInRegularAndScriptAsync( +@"public class MyClass +{ + private int [|_goo|]; + public MyClass() + { + this.E = delegate { this._goo = 0; }; + } + + public Action E; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInLocalFunctionExpressionBodyInCtor() + { + await TestMissingInRegularAndScriptAsync( +@"public class MyClass +{ + private int [|_goo|]; + public MyClass() + { + void LocalFunction() => this._goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInLocalFunctionBlockBodyInCtor() + { + await TestMissingInRegularAndScriptAsync( +@"public class MyClass +{ + private int [|_goo|]; + public MyClass() + { + void LocalFunction() { this._goo = 0; } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInCtor_DifferentInstance() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + MyClass() + { + var goo = new MyClass(); + goo._goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInCtor_DifferentInstance_ObjectInitializer() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + MyClass() + { + var goo = new MyClass { _goo = 0 }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInCtor_QualifiedWithThis() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + MyClass() + { + this._goo = 0; + } +}", +@"class MyClass +{ + private readonly int _goo; + MyClass() + { + this._goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldReturnedInProperty() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + int Goo + { + get { return _goo; } + } +}", +@"class MyClass +{ + private readonly int _goo; + int Goo + { + get { return _goo; } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInProperty() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + int Goo + { + get { return _goo; } + set { _goo = value; } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInMethod() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + int Goo() + { + _goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInNestedTypeConstructor() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + + class Derived : MyClass + { + Derived() + { + _goo = 1; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInNestedTypeMethod() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + + class Derived : MyClass + { + void Method() + { + _goo = 1; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task VariableAssignedToFieldInMethod() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + int Goo() + { + var i = _goo; + } +}", +@"class MyClass +{ + private readonly int _goo; + int Goo() + { + var i = _goo; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldAssignedInMethodWithCompoundOperator() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|] = 0; + int Goo(int value) + { + _goo += value; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldUsedWithPostfixIncrement() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|] = 0; + int Goo(int value) + { + _goo++; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldUsedWithPrefixDecrement() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|] = 0; + int Goo(int value) + { + --_goo; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task AssignedInPartialClass() + { + await TestMissingInRegularAndScriptAsync( +@"partial class MyClass +{ + private int [|_goo|]; +} +partial class MyClass +{ + void SetGoo() + { + _goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task PassedAsParameter() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + void Goo() + { + Bar(_goo); + } + void Bar(int goo) + { + } +}", +@"class MyClass +{ + private readonly int _goo; + void Goo() + { + Bar(_goo); + } + void Bar(int goo) + { + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task PassedAsOutParameter() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + void Goo() + { + int.TryParse(""123"", out _goo); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task PassedAsRefParameter() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + void Goo() + { + Bar(ref _goo); + } + void Bar(ref int goo) + { + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task PassedAsOutParameterInCtor() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + MyClass() + { + int.TryParse(""123"", out _goo); + } +}", +@"class MyClass +{ + private readonly int _goo; + MyClass() + { + int.TryParse(""123"", out _goo); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task PassedAsRefParameterInCtor() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int [|_goo|]; + MyClass() + { + Bar(ref _goo); + } + void Bar(ref int goo) + { + } +}", +@"class MyClass +{ + private readonly int _goo; + MyClass() + { + Bar(ref _goo); + } + void Bar(ref int goo) + { + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task StaticFieldAssignedInStaticCtor() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private static int [|_goo|]; + static MyClass() + { + _goo = 0; + } +}", +@"class MyClass +{ + private static readonly int _goo; + static MyClass() + { + _goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task StaticFieldAssignedInNonStaticCtor() + { + await TestMissingInRegularAndScriptAsync( +@"class MyClass +{ + private static int [|_goo|]; + MyClass() + { + _goo = 0; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldTypeIsMutableStruct() + { + await TestMissingInRegularAndScriptAsync( +@"struct MyStruct +{ + private int _goo; +} +class MyClass +{ + private MyStruct [|_goo|]; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FieldTypeIsCustomImmutableStruct() + { + await TestInRegularAndScriptAsync( +@"struct MyStruct +{ + private readonly int _goo; + private const int _bar = 0; + private static int _fizz; +} +class MyClass +{ + private MyStruct [|_goo|]; +}", +@"struct MyStruct +{ + private readonly int _goo; + private const int _bar = 0; + private static int _fizz; +} +class MyClass +{ + private readonly MyStruct _goo; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FixAll() + { + await TestInRegularAndScriptAsync( +@"class MyClass +{ + private int {|FixAllInDocument:_goo|} = 0, _bar = 0; + private int _x = 0, _y = 0, _z = 0; + private int _fizz = 0; + + void Method() { _z = 1; } +}", +@"class MyClass +{ + private readonly int _goo = 0, _bar = 0; + private readonly int _x = 0; + private readonly int _y = 0; + private int _z = 0; + private readonly int _fizz = 0; + + void Method() { _z = 1; } +}"); + } + + // Remove this test when https://github.com/dotnet/roslyn/issues/25652 is fixed + [Fact] + [Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FixAllDoesNotSupportPartial() + { + await TestMissingInRegularAndScriptAsync( +@" partial struct MyClass + { + private static Func {|FixAllInDocument:_test1|} = x => x > 0; + private static Func _test2 = x => x < 0; + + private static Func _test3 = x => + { + return x == 0; + }; + + private static Func _test4 = x => + { + return x != 0; + }; + } + + partial struct MyClass { }"); + } + + [Fact(Skip = "Partial types not yet supported: https://github.com/dotnet/roslyn/issues/25652")] + [Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task FixAll2() + { + await TestInRegularAndScriptAsync( +@" partial struct MyClass + { + private static Func {|FixAllInDocument:_test1|} = x => x > 0; + private static Func _test2 = x => x < 0; + + private static Func _test3 = x => + { + return x == 0; + }; + + private static Func _test4 = x => + { + return x != 0; + }; + } + + partial struct MyClass { }", +@" partial struct MyClass + { + private static readonly Func _test1 = x => x > 0; + private static readonly Func _test2 = x => x < 0; + + private static readonly Func _test3 = x => + { + return x == 0; + }; + + private static readonly Func _test4 = x => + { + return x != 0; + }; + } + + partial struct MyClass { }"); + } + } +} diff --git a/src/EditorFeatures/VisualBasicTest/MakeFieldReadonly/MakeFieldReadonlyTests.vb b/src/EditorFeatures/VisualBasicTest/MakeFieldReadonly/MakeFieldReadonlyTests.vb new file mode 100644 index 0000000000000000000000000000000000000000..85fe0781517d8b4deebc1b34eeb825e44baafd82 --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/MakeFieldReadonly/MakeFieldReadonlyTests.vb @@ -0,0 +1,738 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic.MakeFieldReadonly +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.MakeFieldReadonly + Public Class MakeFieldReadonlyTests + Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest + + Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) + Return (New VisualBasicMakeFieldReadonlyDiagnosticAnalyzer(), + New VisualBasicMakeFieldReadonlyCodeFixProvider()) + End Function + + + + + + + Public Async Function FieldIsPublic(accessibility As String) As Task + Await TestMissingInRegularAndScriptAsync( +$"Class C + {accessibility} [|_goo|] As Integer +End Class") + End Function + + + Public Async Function FieldIsEvent() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private Event [|SomeEvent|]() +End Class") + End Function + + + Public Async Function FieldIsReadonly() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private ReadOnly [|_goo|] As Integer +End Class") + End Function + + + Public Async Function FieldIsConst() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private Const [|_goo|] As Integer +End Class") + End Function + + + Public Async Function FieldNotAssigned() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer +End Class", +"Class C + Private ReadOnly _goo As Integer +End Class") + End Function + + ' Update this test when https://github.com/dotnet/roslyn/issues/25652 is fixed + + Public Async Function FieldNotAssigned_PartialClass1() As Task + Await TestMissingInRegularAndScriptAsync( +"Partial Class C + Private [|_goo|] As Integer +End Class") + End Function + + ' Update this test when https://github.com/dotnet/roslyn/issues/25652 is fixed + + Public Async Function FieldNotAssigned_PartialClass2() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer +End Class + +Partial Class C +EndClass") + End Function + + + Public Async Function FieldNotAssigned_Struct() As Task + Await TestInRegularAndScriptAsync( +"Structure C + Private [|_goo|] As Integer +End Structure", +"Structure C + Private ReadOnly _goo As Integer +End Structure") + End Function + + + Public Async Function FieldNotAssigned_Module() As Task + Await TestInRegularAndScriptAsync( +"Module C + Private [|_goo|] As Integer +End Module", +"Module C + Private ReadOnly _goo As Integer +End Module") + End Function + + + Public Async Function FieldNotAssigned_FieldDeclaredWithDim() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim [|_goo|] As Integer +End Class", +"Class C + ReadOnly _goo As Integer +End Class") + End Function + + + Public Async Function FieldAssignedInline() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_AllCanBeReadonly() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0, _bar As Integer = 0 +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + Private _bar As Integer = 0 +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_AllCanBeReadonly_MultipleNamesInDeclarator() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|], _bar As Integer, _fizz As String = """" +End Class", +"Class C + Private ReadOnly _goo As Integer + Private _bar As Integer + Private _fizz As String = """" +End Class") + End Function + + + Public Async Function ThreeFieldsAssignedInline_AllCanBeReadonly_SeparatesAllAndKeepsThemInOrder() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private _goo As Integer = 0, [|_bar|] As Integer = 0, _fizz As Integer = 0 +End Class", +"Class C + Private _goo As Integer = 0 + Private ReadOnly _bar As Integer = 0 + Private _fizz As Integer = 0 +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms01() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim [|x|] As Integer, y As String +End Class", +"Class C + Private ReadOnly x As Integer + Private y As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms02() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x As Integer, [|y|] As String +End Class", +"Class C + Private x As Integer + Private ReadOnly y As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms03() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim [|x|], y As Integer, z, w As String +End Class", +"Class C + Private ReadOnly x As Integer + Private y As Integer + Private z As String + Private w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms04() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, [|y|] As Integer, z, w As String +End Class", +"Class C + Private x As Integer + Private ReadOnly y As Integer + Private z As String + Private w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms05() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, y As Integer, [|z|], w As String +End Class", +"Class C + Private x As Integer + Private y As Integer + Private ReadOnly z As String + Private w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms06() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, y As Integer, z, [|w|] As String +End Class", +"Class C + Private x As Integer + Private y As Integer + Private z As String + Private ReadOnly w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms07() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim [|x|], y() As Integer, z(), w As String +End Class", +"Class C + Private ReadOnly x As Integer + Private y As Integer() + Private z As String() + Private w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms08() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, [|y|]() As Integer, z(), w As String +End Class", +"Class C + Private x As Integer + Private ReadOnly y As Integer() + Private z As String() + Private w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms09() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, y() As Integer, [|z|](), w As String +End Class", +"Class C + Private x As Integer + Private y As Integer() + Private ReadOnly z As String() + Private w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms10() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, y() As Integer, z(), [|w|] As String +End Class", +"Class C + Private x As Integer + Private y As Integer() + Private z As String() + Private ReadOnly w As String +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms11() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim [|x|] As New String("""".ToCharArray) +End Class", +"Class C + ReadOnly x As New String("""".ToCharArray) +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms12() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim [|x|], y As New String("""".ToCharArray) +End Class", +"Class C + Private ReadOnly x As String = New String("""".ToCharArray) + Private y As String = New String("""".ToCharArray) +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms13() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim x, [|y|] As New String("""".ToCharArray) +End Class", +"Class C + Private x As String = New String("""".ToCharArray) + Private ReadOnly y As String = New String("""".ToCharArray) +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_VBSpecialForms14() As Task + Await TestInRegularAndScriptAsync( +"Class C + Dim {|FixAllInDocument:x|}, y As New String("""".ToCharArray) +End Class", +"Class C + ReadOnly x, y As New String("""".ToCharArray) +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_OneAssignedInMethod() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private _goo As Integer = 0, [|_bar|] As Integer = 0 + Private Sub Goo() + _goo = 0 + End Sub +End Class", +"Class C + Private _goo As Integer = 0 + Private ReadOnly _bar As Integer = 0 + Private Sub Goo() + _goo = 0 + End Sub +End Class") + End Function + + + Public Async Function MultipleFieldsAssignedInline_NoInitializer() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer, _bar As Integer = 0 +End Class", +"Class C + Private ReadOnly _goo As Integer + Private _bar As Integer = 0 +End Class") + End Function + + + Public Async Function FieldAssignedInCtor() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Public Sub New() + _goo = 0 + End Sub +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + Public Sub New() + _goo = 0 + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInMultilineLambdaInCtor() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + + Public Event SomeEvent() + + Public Sub New() + AddHandler SomeEvent, Sub() + Me._goo = 0 + End Sub + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInLambdaInCtor() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + + Public Event SomeEvent() + + Public Sub New() + AddHandler SomeEvent, Sub() Me._goo = 0 + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInCtor_DifferentInstance() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Public Sub New() + Dim bar = New C() + bar._goo = 0 + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInCtor_DifferentInstance_QualifiedWithObjectInitializer() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Public Sub New() + Dim bar = New C() With { + ._goo = 0 + } + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInCtor_QualifiedWithMe() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Public Sub New() + Me._goo = 0 + End Sub +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + Public Sub New() + Me._goo = 0 + End Sub +End Class") + End Function + + + Public Async Function FieldReturnedInProperty() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + ReadOnly Property Goo As Integer + Get + Return _goo + End Get + End Property +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + ReadOnly Property Goo As Integer + Get + Return _goo + End Get + End Property +End Class") + End Function + + + Public Async Function FieldAssignedInProperty() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + ReadOnly Property Goo As Integer + Get + Return _goo + End Get + Set(value As Integer) + _goo = value + End Set + End Property +End Class") + End Function + + + Public Async Function FieldAssignedInMethod() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Sub Goo + _goo = 0 + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInNestedTypeConstructor() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Class Derived + Inherits C + + Sub New + _goo = 0 + End Sub + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInNestedTypeMethod() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Class Derived + Inherits C + + Sub Method + _goo = 0 + End Sub + End Sub +End Class") + End Function + + + Public Async Function VariableAssignedToFieldInMethod() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Sub Goo + Dim i = _goo + End Sub +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + Sub Goo + Dim i = _goo + End Sub +End Class") + End Function + + + Public Async Function FieldAssignedInMethodWithCompoundOperator() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Sub Goo + _goo += 0 + End Sub +End Class") + End Function + + + Public Async Function AssignedInPartialClass() As Task + Await TestMissingInRegularAndScriptAsync( +"Partial Class C + Private [|_goo|] As Integer = 0 +End Class + +Partial Class C + Sub Goo() + _goo = 0 + End Sub +End Class") + End Function + + + Public Async Function PassedAsByRefParameter() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Sub Goo() + Bar(_goo) + End Sub + Sub Bar(ByRef value As Integer) + End Sub +End Class") + End Function + + + Public Async Function PassedAsByRefParameterInCtor() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Sub New() + Bar(_goo) + End Sub + Sub Bar(ByRef value As Integer) + End Sub +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + Sub New() + Bar(_goo) + End Sub + Sub Bar(ByRef value As Integer) + End Sub +End Class") + End Function + + + Public Async Function PassedAsByValParameter() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private [|_goo|] As Integer = 0 + Sub Goo() + Bar(_goo) + End Sub + Sub Bar(ByVal value As Integer) + End Sub +End Class", +"Class C + Private ReadOnly _goo As Integer = 0 + Sub Goo() + Bar(_goo) + End Sub + Sub Bar(ByVal value As Integer) + End Sub +End Class") + End Function + + + Public Async Function SharedFieldAssignedInSharedCtor() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private Shared [|_goo|] As Integer = 0 + Shared Sub New() + _goo = 0 + End Sub +End Class", +"Class C + Private Shared ReadOnly _goo As Integer = 0 + Shared Sub New() + _goo = 0 + End Sub +End Class") + End Function + + + Public Async Function SharedFieldAssignedInNonSharedCtor() As Task + Await TestMissingInRegularAndScriptAsync( +"Class C + Private Shared [|_goo|] As Integer = 0 + Sub New() + _goo = 0 + End Sub +End Class") + End Function + + + Public Async Function FieldIsMutableStructure() As Task + Await TestMissingInRegularAndScriptAsync( +"Structure S + Private _goo As Integer +End Structure +Class C + Private [|_goo|] As S +End Class") + End Function + + + Public Async Function FieldIsCustomImmutableStructure() As Task + Await TestInRegularAndScriptAsync( +"Structure S + Private readonly _goo As Integer + Private Const _bar As Integer = 0 + Private Shared _fizz As Integer +End Structure +Class C + Private [|_goo|] As S +End Class", +"Structure S + Private readonly _goo As Integer + Private Const _bar As Integer = 0 + Private Shared _fizz As Integer +End Structure +Class C + Private ReadOnly _goo As S +End Class") + End Function + + + Public Async Function FixAll() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private {|FixAllInDocument:_goo|} As Integer = 0, _bar As Integer = 0 + Dim a, b(), c As Integer, x, y As String + Private _fizz As Integer = 0 +End Class", +"Class C + Private ReadOnly _goo As Integer = 0, _bar As Integer = 0 + ReadOnly a, b(), c As Integer, x, y As String + Private ReadOnly _fizz As Integer = 0 +End Class") + End Function + + + Public Async Function FixAll_MultipleFieldsAssignedInline_TwoCanBeReadonly_MultipleNamesInDeclarator() As Task + Await TestInRegularAndScriptAsync( +"Class C + Private _goo, {|FixAllInDocument:_bar|} As Integer, _fizz As String = """" + Sub Goo() + _goo = 0 + End Sub +End Class", +"Class C + Private _goo As Integer + Private ReadOnly _bar As Integer + Private ReadOnly _fizz As String = """" + Sub Goo() + _goo = 0 + End Sub +End Class") + End Function + End Class +End Namespace diff --git a/src/Features/CSharp/Portable/MakeFieldReadonly/CSharpMakeFieldReadonlyCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeFieldReadonly/CSharpMakeFieldReadonlyCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..125245741231d0c3f60346fa00950dae6ee30ff5 --- /dev/null +++ b/src/Features/CSharp/Portable/MakeFieldReadonly/CSharpMakeFieldReadonlyCodeFixProvider.cs @@ -0,0 +1,21 @@ +// 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.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.MakeFieldReadonly; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.MakeFieldReadonly +{ + [ExportCodeFixProvider(LanguageNames.CSharp), Shared] + internal class CSharpMakeFieldReadonlyCodeFixProvider : AbstractMakeFieldReadonlyCodeFixProvider + { + protected override SyntaxNode GetInitializerNode(VariableDeclaratorSyntax declaration) + => declaration.Initializer?.Value; + + protected override ImmutableList GetVariableDeclarators(FieldDeclarationSyntax fieldDeclaration) + => fieldDeclaration.Declaration.Variables.ToImmutableListOrEmpty(); + } +} diff --git a/src/Features/CSharp/Portable/MakeFieldReadonly/CSharpMakeFieldReadonlyDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/MakeFieldReadonly/CSharpMakeFieldReadonlyDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..4d6f7162d947ff91a2e207c34c1edbcffdf35ac2 --- /dev/null +++ b/src/Features/CSharp/Portable/MakeFieldReadonly/CSharpMakeFieldReadonlyDiagnosticAnalyzer.cs @@ -0,0 +1,65 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.MakeFieldReadonly; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp.MakeFieldReadonly +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class CSharpMakeFieldReadonlyDiagnosticAnalyzer : + AbstractMakeFieldReadonlyDiagnosticAnalyzer + { + protected override ISyntaxFactsService GetSyntaxFactsService() + => CSharpSyntaxFactsService.Instance; + + protected override bool IsWrittenTo(IdentifierNameSyntax name, SemanticModel model, CancellationToken cancellationToken) + => name.IsWrittenTo(); + + protected override bool IsMemberOfThisInstance(SyntaxNode node) + { + // if it is a qualified name, make sure it is `this.name` + if (node.Parent is MemberAccessExpressionSyntax memberAccess) + { + return memberAccess.Expression is ThisExpressionSyntax; + } + + // make sure it isn't in an object initializer + if (node.Parent.Parent is InitializerExpressionSyntax) + { + return false; + } + + return true; + } + + protected override void AddCandidateTypesInCompilationUnit(SemanticModel semanticModel, SyntaxNode compilationUnit, PooledHashSet<(ITypeSymbol, SyntaxNode)> candidateTypes, CancellationToken cancellationToken) + { + foreach (var node in compilationUnit.DescendantNodes(descendIntoChildren: n => IsContainerOrAnalyzableType(n))) + { + if (node.IsKind(SyntaxKind.ClassDeclaration, out BaseTypeDeclarationSyntax baseTypeDeclaration) || + node.IsKind(SyntaxKind.StructDeclaration, out baseTypeDeclaration)) + { + // Walk to the root to see if the current or any containing type is non-partial + for (var current = baseTypeDeclaration; current != null; current = current.Parent as BaseTypeDeclarationSyntax) + { + if (!current.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + candidateTypes.Add((semanticModel.GetDeclaredSymbol(baseTypeDeclaration, cancellationToken), node)); + break; + } + } + } + } + } + + private static bool IsContainerOrAnalyzableType(SyntaxNode node) + { + return node.IsKind(SyntaxKind.CompilationUnit, SyntaxKind.NamespaceDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration); + } + } +} diff --git a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs index 69adbf08fd91ae3c36817c33cfcf9a5d043be1d8..4d5b57ba0bd0f57e2bae94313bd32fb6db4e8a78 100644 --- a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -33,6 +33,7 @@ internal static class PredefinedCodeFixProviderNames public const string ImplementAbstractClass = nameof(ImplementAbstractClass); public const string ImplementInterface = nameof(ImplementInterface); public const string InsertMissingCast = nameof(InsertMissingCast); + public const string MakeFieldReadonly = nameof(MakeFieldReadonly); public const string MakeMethodSynchronous = nameof(MakeMethodSynchronous); public const string MoveToTopOfFile = nameof(MoveToTopOfFile); public const string PopulateSwitch = nameof(PopulateSwitch); diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index 0439d77deb54b00014a3dd7c3c5aad8b8c625704..13a5a70a0fc55d5779426752ac90631a5e00041d 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -64,6 +64,8 @@ internal static class IDEDiagnosticIds public const string ValidateFormatStringDiagnosticID = "IDE0043"; + public const string MakeFieldReadonlyDiagnosticId = "IDE0044"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index b702c0f526d1741506c15e4f80117f9028f78cdb..4649b6034d680ef96e77327e5c14ee8616376ed0 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -251,6 +251,15 @@ internal class FeaturesResources { } } + /// + /// Looks up a localized string similar to Add readonly modifier. + /// + internal static string Add_readonly_modifier { + get { + return ResourceManager.GetString("Add_readonly_modifier", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add reference to '{0}'.. /// @@ -2007,6 +2016,15 @@ internal class FeaturesResources { } } + /// + /// Looks up a localized string similar to Make field readonly. + /// + internal static string Make_field_readonly { + get { + return ResourceManager.GetString("Make_field_readonly", resourceCulture); + } + } + /// /// Looks up a localized string similar to Make method synchronous. /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 5e8c0429c9265ce248d4f760744fd93d2972ea10..b085efcaa1e5ba96f0546cc2384ebf9b938e108f 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1346,4 +1346,10 @@ This version used in: {2} Warning: Collection may be modified during iteration. - \ No newline at end of file + + Add readonly modifier + + + Make field readonly + + diff --git a/src/Features/Core/Portable/MakeFieldReadonly/AbstractMakeFieldReadonlyCodeFixProvider.cs b/src/Features/Core/Portable/MakeFieldReadonly/AbstractMakeFieldReadonlyCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0a42584b75d6643a1fdcfe942a52f5142299fe8 --- /dev/null +++ b/src/Features/Core/Portable/MakeFieldReadonly/AbstractMakeFieldReadonlyCodeFixProvider.cs @@ -0,0 +1,110 @@ +// 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.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.MakeFieldReadonly +{ + internal abstract class AbstractMakeFieldReadonlyCodeFixProvider + : SyntaxEditorBasedCodeFixProvider + where TSymbolSyntax : SyntaxNode + where TFieldDeclarationSyntax : SyntaxNode + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(IDEDiagnosticIds.MakeFieldReadonlyDiagnosticId); + + protected abstract SyntaxNode GetInitializerNode(TSymbolSyntax declaration); + protected abstract ImmutableList GetVariableDeclarators(TFieldDeclarationSyntax declaration); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + context.RegisterCodeFix(new MyCodeAction( + c => FixAsync(context.Document, context.Diagnostics[0], c)), + context.Diagnostics); + return SpecializedTasks.EmptyTask; + } + + private async Task FixWithEditorAsync( + Document document, SyntaxEditor editor, ImmutableArray diagnostics, + CancellationToken cancellationToken) + { + var declarators = new List(); + + foreach (var diagnostic in diagnostics) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + declarators.Add(root.FindNode(diagnosticSpan).FirstAncestorOrSelf()); + } + + await MakeFieldReadonlyAsync(document, editor, declarators).ConfigureAwait(false); + } + + private async Task MakeFieldReadonlyAsync(Document document, SyntaxEditor editor, List declarators) + { + var declaratorsByField = declarators.GroupBy(g => g.FirstAncestorOrSelf()); + + foreach (var fieldDeclarators in declaratorsByField) + { + var declarationDeclarators = GetVariableDeclarators(fieldDeclarators.Key); + + if (declarationDeclarators.Count == fieldDeclarators.Count()) + { + editor.SetModifiers(fieldDeclarators.Key, editor.Generator.GetModifiers(fieldDeclarators.Key) | DeclarationModifiers.ReadOnly); + } + else + { + var model = await document.GetSemanticModelAsync().ConfigureAwait(false); + var generator = editor.Generator; + + foreach (var declarator in declarationDeclarators.Reverse()) + { + var symbol = (IFieldSymbol)model.GetDeclaredSymbol(declarator); + var modifiers = generator.GetModifiers(fieldDeclarators.Key); + + var newDeclaration = generator.FieldDeclaration(symbol.Name, + generator.TypeExpression(symbol.Type), + Accessibility.Private, + fieldDeclarators.Contains(declarator) + ? modifiers | DeclarationModifiers.ReadOnly + : modifiers, + GetInitializerNode(declarator)) + .WithAdditionalAnnotations(Formatter.Annotation); + + editor.InsertAfter(fieldDeclarators.Key, newDeclaration); + } + + editor.RemoveNode(fieldDeclarators.Key); + } + } + } + + protected override Task FixAllAsync( + Document document, + ImmutableArray diagnostics, + SyntaxEditor editor, + CancellationToken cancellationToken) + { + return FixWithEditorAsync(document, editor, diagnostics, cancellationToken); + } + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(Func> createChangedDocument) : + base(FeaturesResources.Add_readonly_modifier, createChangedDocument) + { + } + } + } +} diff --git a/src/Features/Core/Portable/MakeFieldReadonly/AbstractMakeFieldReadonlyDiagnosticAnalyzer.cs b/src/Features/Core/Portable/MakeFieldReadonly/AbstractMakeFieldReadonlyDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..6cc478c0b11f06ef90d97c0bc54362dd2b2a2a20 --- /dev/null +++ b/src/Features/Core/Portable/MakeFieldReadonly/AbstractMakeFieldReadonlyDiagnosticAnalyzer.cs @@ -0,0 +1,214 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.MakeFieldReadonly +{ + internal abstract class AbstractMakeFieldReadonlyDiagnosticAnalyzer + : AbstractCodeStyleDiagnosticAnalyzer + where TIdentifierNameSyntax : SyntaxNode + where TConstructorDeclarationSyntax : SyntaxNode + { + protected AbstractMakeFieldReadonlyDiagnosticAnalyzer() + : base( + IDEDiagnosticIds.MakeFieldReadonlyDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Add_readonly_modifier), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Make_field_readonly), WorkspacesResources.ResourceManager, typeof(WorkspacesResources))) + { + } + + protected abstract ISyntaxFactsService GetSyntaxFactsService(); + protected abstract bool IsWrittenTo(TIdentifierNameSyntax node, SemanticModel model, CancellationToken cancellationToken); + protected abstract bool IsMemberOfThisInstance(SyntaxNode node); + + protected abstract void AddCandidateTypesInCompilationUnit(SemanticModel semanticModel, SyntaxNode compilationUnit, PooledHashSet<(ITypeSymbol, SyntaxNode)> candidateTypes, CancellationToken cancellationToken); + + public override bool OpenFileOnly(Workspace workspace) => false; + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + + protected sealed override void InitializeWorker(AnalysisContext context) + => context.RegisterSemanticModelAction(AnalyzeSemanticModel); + + private void AnalyzeSemanticModel(SemanticModelAnalysisContext context) + { + var cancellationToken = context.CancellationToken; + var semanticModel = context.SemanticModel; + + // Early return if user disables the feature + var optionSet = context.Options.GetDocumentOptionSetAsync(semanticModel.SyntaxTree, cancellationToken).GetAwaiter().GetResult(); + if (optionSet == null) + { + return; + } + + var option = optionSet.GetOption(CodeStyleOptions.PreferReadonly, semanticModel.Language); + if (!option.Value) + { + return; + } + + var syntaxFactsService = GetSyntaxFactsService(); + var root = semanticModel.SyntaxTree.GetRoot(cancellationToken); + var candidateTypes = PooledHashSet<(ITypeSymbol, SyntaxNode)>.GetInstance(); + AddCandidateTypesInCompilationUnit(semanticModel, root, candidateTypes, cancellationToken); + + var candidateFields = PooledHashSet.GetInstance(); + foreach (var (typeSymbol, typeSyntax) in candidateTypes) + { + AddCandidateFieldsInType(context, typeSymbol, typeSyntax, candidateFields); + } + + if (candidateFields.Count > 0) + { + var analyzedTypes = ArrayBuilder<(ITypeSymbol, SyntaxNode)>.GetInstance(candidateTypes.Count); + analyzedTypes.AddRange(candidateTypes); + analyzedTypes.Sort((x, y) => x.Item2.SpanStart - y.Item2.SpanStart); + + // Remove types from the analysis list when they are contained by another type in the list + for (var i = analyzedTypes.Count - 1; i >= 1; i--) + { + if (analyzedTypes[i - 1].Item2.FullSpan.Contains(analyzedTypes[i].Item2.FullSpan)) + { + analyzedTypes[i] = default; + } + } + + foreach (var (typeSymbol, typeSyntax) in candidateTypes) + { + if (typeSyntax is null) + { + // Node was removed due to nested scoping + continue; + } + + RemoveAssignedSymbols(semanticModel, syntaxFactsService, typeSyntax, candidateFields, cancellationToken); + } + + foreach (var symbol in candidateFields) + { + var diagnostic = Diagnostic.Create( + GetDescriptorWithSeverity(option.Notification.Value), + symbol.Locations[0]); + context.ReportDiagnostic(diagnostic); + } + + analyzedTypes.Free(); + } + + candidateTypes.Free(); + candidateFields.Free(); + } + + private void AddCandidateFieldsInType(SemanticModelAnalysisContext context, ITypeSymbol typeSymbol, SyntaxNode typeSyntax, PooledHashSet candidateFields) + { + foreach (var item in typeSymbol.GetMembers()) + { + if (item is IFieldSymbol symbol && + symbol.DeclaredAccessibility == Accessibility.Private && + !symbol.IsReadOnly && + !symbol.IsConst && + !symbol.IsImplicitlyDeclared && + !IsMutableValueType(symbol.Type)) + { + candidateFields.Add(symbol); + } + } + } + + private void RemoveAssignedSymbols(SemanticModel model, ISyntaxFactsService syntaxFactsService, SyntaxNode node, PooledHashSet unassignedSymbols, CancellationToken cancellationToken) + { + foreach (var descendant in node.DescendantNodes()) + { + if (unassignedSymbols.Count == 0) + { + return; + } + + if (!(descendant is TIdentifierNameSyntax name)) + { + continue; + } + + var symbol = model.GetSymbolInfo(descendant).Symbol as IFieldSymbol; + if (symbol == null || !unassignedSymbols.Contains(symbol)) + { + continue; + } + + if (!IsMemberOfThisInstance(descendant)) + { + unassignedSymbols.Remove(symbol); + } + + if (IsDescendentOf(descendant, out var ctorNode)) + { + var isInAnonymousOrLocalFunction = false; + for (var current = descendant.Parent; current != ctorNode; current = current.Parent) + { + if (syntaxFactsService.IsAnonymousFunction(current) || syntaxFactsService.IsLocalFunction(current)) + { + isInAnonymousOrLocalFunction = true; + break; + } + } + + if (isInAnonymousOrLocalFunction) + { + unassignedSymbols.Remove(symbol); + } + else + { + var ctorSymbol = model.GetDeclaredSymbol(ctorNode); + if (!ctorSymbol.ContainingType.Equals(symbol.ContainingType)) + { + unassignedSymbols.Remove(symbol); + } + + if (!ctorSymbol.IsStatic && symbol.IsStatic) + { + unassignedSymbols.Remove(symbol); + } + } + + // assignments in the ctor don't matter other than the static modifiers and lambdas point checked above + continue; + } + + if (IsWrittenTo(name, model, cancellationToken)) + { + unassignedSymbols.Remove(symbol); + } + } + } + + private bool IsDescendentOf(SyntaxNode node, out T ctor) where T : SyntaxNode + { + ctor = node.FirstAncestorOrSelf(); + return ctor != null; + } + + private bool IsMutableValueType(ITypeSymbol type) + { + if (type.TypeKind != TypeKind.Struct) + { + return false; + } + + foreach (var member in type.GetMembers()) + { + if (member is IFieldSymbol fieldSymbol && + !(fieldSymbol.IsConst || fieldSymbol.IsReadOnly || fieldSymbol.IsStatic)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 63dc5a200de4c0159433d6c72d5f52737fef2754..6f5ce26175f9f55fee9f7ed46e05284b2e43a767 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -2005,6 +2005,16 @@ Tato verze se používá zde: {2}. Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 528dfb39d8ee93f16fe6d95ca1503b697c3884f9..e872d449a2f986b908af01dff76dba335cc058db 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -2005,6 +2005,16 @@ Diese Version wird verwendet in: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 9f0130f97dca0fc61f96c43bebb049204d04eb27..45af4bc870eb520657b69a4af63e9f3461395590 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -2005,6 +2005,16 @@ Esta versión se utiliza en: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index f149b373fbf8f79253d0596fb3da920f50bc6022..b3d4607dc52067838c09febd7ac8b3286af1253e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -2005,6 +2005,16 @@ Version utilisée dans : {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index d0575f68125424841c40f24db9e58623e005ae9d..bce358c5cbfda10cd04952023b189949f75aa8c1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -2005,6 +2005,16 @@ Questa versione è usata {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index fc02d195ca4bb66f4ecd1fb69bf2d42208c99c15..d13db4469ddc648ef1a1420e43fead99de07403b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -2005,6 +2005,16 @@ This version used in: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 152399e52dedb8a261a88cfeb6f2bdd5af56b560..c4fb8ae61b942531128592178e1751355356a431 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -2005,6 +2005,16 @@ This version used in: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 5d4c1680d1c814b3187d8cc7476d5655f18f08d6..0dab9643b4646c789f6b8e79dfb5d5fab1c5119c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -2005,6 +2005,16 @@ Ta wersja jest używana wersja: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index c7f03d82b716473038dc1c09b79a690b1c6d1a34..2f46cd49df4c55d35272fc2b12d9b01c0c0ece0a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -2005,6 +2005,16 @@ Essa versão é usada no: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 7fc7b593150ce92a697186517e384bda933c9407..13d6e0367c50b25dd103930ea7ce717398bc582b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -2005,6 +2005,16 @@ This version used in: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 2e25659a71ec4db6767207dc70c6a567ff672852..e90e3c40a7fcdcb7f978bb0d1dbdbd60fe6832b4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -2005,6 +2005,16 @@ Bu sürüm şurada kullanılır: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 77f3b25119362a0e75babb42029b31d149645d97..632e5c89c0674fdba96148d2b4d1f5a1f7bd0eba 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -2005,6 +2005,16 @@ This version used in: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 4ce29b17cb5168814aa0dcfc6000331e38103eba..7b983eef037c1ea866c2c7d3a4d2b7dc7b46e81d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -2005,6 +2005,16 @@ This version used in: {2} Warning: Collection may be modified during iteration. + + Add readonly modifier + Add readonly modifier + + + + Make field readonly + Make field readonly + + \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/MakeFieldReadonly/VisualBasicMakeFieldReadonlyCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeFieldReadonly/VisualBasicMakeFieldReadonlyCodeFixProvider.vb new file mode 100644 index 0000000000000000000000000000000000000000..a29f8bfd1e2bb876bd481c009d3fd0e46bd2ff1f --- /dev/null +++ b/src/Features/VisualBasic/Portable/MakeFieldReadonly/VisualBasicMakeFieldReadonlyCodeFixProvider.vb @@ -0,0 +1,27 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.Composition +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.MakeFieldReadonly +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.MakeFieldReadonly + + Friend Class VisualBasicMakeFieldReadonlyCodeFixProvider + Inherits AbstractMakeFieldReadonlyCodeFixProvider(Of ModifiedIdentifierSyntax, FieldDeclarationSyntax) + + Protected Overrides Function GetVariableDeclarators(declaration As FieldDeclarationSyntax) As ImmutableList(Of ModifiedIdentifierSyntax) + Return declaration.Declarators.SelectMany(Function(d) d.Names).ToImmutableListOrEmpty() + End Function + + Protected Overrides Function GetInitializerNode(declaration As ModifiedIdentifierSyntax) As SyntaxNode + Dim initializer = CType(declaration.Parent, VariableDeclaratorSyntax).Initializer?.Value + If initializer Is Nothing Then + initializer = TryCast(CType(declaration.Parent, VariableDeclaratorSyntax).AsClause, AsNewClauseSyntax)?.NewExpression + End If + + Return initializer + End Function + End Class +End Namespace diff --git a/src/Features/VisualBasic/Portable/MakeFieldReadonly/VisualBasicMakeFieldReadonlyDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/MakeFieldReadonly/VisualBasicMakeFieldReadonlyDiagnosticAnalyzer.vb new file mode 100644 index 0000000000000000000000000000000000000000..4f260e4e13f480dd7281895e0536f56d841f207c --- /dev/null +++ b/src/Features/VisualBasic/Portable/MakeFieldReadonly/VisualBasicMakeFieldReadonlyDiagnosticAnalyzer.vb @@ -0,0 +1,68 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Threading +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.LanguageServices +Imports Microsoft.CodeAnalysis.MakeFieldReadonly +Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.MakeFieldReadonly + + Friend Class VisualBasicMakeFieldReadonlyDiagnosticAnalyzer + Inherits AbstractMakeFieldReadonlyDiagnosticAnalyzer(Of IdentifierNameSyntax, ConstructorBlockSyntax) + + Protected Overrides Function GetSyntaxFactsService() As ISyntaxFactsService + Return VisualBasicSyntaxFactsService.Instance + End Function + + Protected Overrides Function IsWrittenTo(name As IdentifierNameSyntax, model As SemanticModel, cancellationToken As CancellationToken) As Boolean + Return name.IsWrittenTo(model, cancellationToken) + End Function + + Protected Overrides Function IsMemberOfThisInstance(node As SyntaxNode) As Boolean + ' if it is a qualified name, make sure it is `Me.name` + Dim memberAccess = TryCast(node.Parent, MemberAccessExpressionSyntax) + If memberAccess IsNot Nothing Then + Return TryCast(memberAccess.Expression, MeExpressionSyntax) IsNot Nothing + End If + + ' make sure it isn't in an object initializer + If TryCast(node.Parent.Parent, ObjectCreationInitializerSyntax) IsNot Nothing Then + Return False + End If + + Return True + End Function + + Protected Overrides Sub AddCandidateTypesInCompilationUnit(semanticModel As SemanticModel, compilationUnit As SyntaxNode, candidateTypes As PooledHashSet(Of (ITypeSymbol, SyntaxNode)), cancellationToken As CancellationToken) + For Each node In compilationUnit.DescendantNodes(descendIntoChildren:=Function(n) IsContainerOrAnalyzableType(n)) + Dim typeBlock As TypeBlockSyntax + If node.IsKind(SyntaxKind.ModuleBlock, SyntaxKind.ClassBlock, SyntaxKind.StructureBlock) Then + typeBlock = DirectCast(node, TypeBlockSyntax) + Else + Continue For + End If + + Dim current = typeBlock + While current IsNot Nothing + If Not current.GetModifiers.Any(SyntaxKind.PartialKeyword) Then + ' VB allows one block to omit the Partial keyword + Dim currentSymbol = semanticModel.GetDeclaredSymbol(current) + If currentSymbol.DeclaringSyntaxReferences.Length = 1 Then + Dim addedSymbol = If(current Is typeBlock, currentSymbol, semanticModel.GetDeclaredSymbol(typeBlock)) + candidateTypes.Add((addedSymbol, typeBlock)) + End If + End If + + current = TryCast(current.Parent, TypeBlockSyntax) + End While + Next + End Sub + + Private Shared Function IsContainerOrAnalyzableType(node As SyntaxNode) As Boolean + Return node.IsKind(SyntaxKind.CompilationUnit, SyntaxKind.NamespaceBlock) OrElse + node.IsKind(SyntaxKind.ModuleBlock, SyntaxKind.ClassBlock, SyntaxKind.StructureBlock) + End Function + End Class +End Namespace diff --git a/src/Test/Utilities/Portable/Traits/Traits.cs b/src/Test/Utilities/Portable/Traits/Traits.cs index d1f1cf83c9619b54c297d9264f6214435936848a..0d580d546722377a9cd42fab1a1473f72654a821 100644 --- a/src/Test/Utilities/Portable/Traits/Traits.cs +++ b/src/Test/Utilities/Portable/Traits/Traits.cs @@ -83,6 +83,7 @@ public static class Features public const string CodeActionsInvertIf = "CodeActions.InvertIf"; public const string CodeActionsInvokeDelegateWithConditionalAccess = "CodeActions.InvokeDelegateWithConditionalAccess"; public const string CodeActionsLambdaSimplifier = "CodeActions.LambdaSimplifier"; + public const string CodeActionsMakeFieldReadonly = "CodeActions.MakeFieldReadonly"; public const string CodeActionsMakeMethodAsynchronous = "CodeActions.MakeMethodAsynchronous"; public const string CodeActionsMakeMethodSynchronous = "CodeActions.MakeMethodSynchronous"; public const string CodeActionsMoveDeclarationNearReference = "CodeActions.MoveDeclarationNearReference"; diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs index 0ccb5cb88b52a1028b01d925d48aa3df65f544e8..8f57486d147bfc9229b8ea5c30eaea18c5d951cd 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs @@ -719,6 +719,12 @@ public string Style_PreferBraces set { SetXmlOption(CSharpCodeStyleOptions.PreferBraces, value); } } + public string Style_PreferReadonly + { + get { return GetXmlOption(CodeStyleOptions.PreferReadonly); } + set { SetXmlOption(CodeStyleOptions.PreferReadonly, value); } + } + public int Wrapping_IgnoreSpacesAroundBinaryOperators { get diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index cbad458e75f6ce4f540bced42ab918580a8af63c..05630a82251030b8bc795fa6886d7537c370546b 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -781,6 +781,21 @@ class List public T this[int i] { get { return _values[i]; } } } //] +"; + + private static readonly string s_preferReadonly = $@" +class Customer +{{ +//[ + // {ServicesVSResources.Prefer_colon} + // '_value' can only be assigned in constructor + private readonly int _value = 0; + + // {ServicesVSResources.Over_colon} + // '_value' can be assigned anywhere + private int _value = 0; +//] +}} "; #endregion @@ -794,6 +809,7 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) : var predefinedTypesGroupTitle = CSharpVSResources.predefined_type_preferences_colon; var varGroupTitle = CSharpVSResources.var_preferences_colon; var nullCheckingGroupTitle = CSharpVSResources.null_checking_colon; + var fieldGroupTitle = ServicesVSResources.Field_preferences_colon; var codeBlockPreferencesGroupTitle = ServicesVSResources.Code_block_preferences_colon; var expressionPreferencesGroupTitle = ServicesVSResources.Expression_preferences_colon; var variablePreferencesGroupTitle = ServicesVSResources.Variable_preferences_colon; @@ -856,6 +872,9 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) : CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferCoalesceExpression, ServicesVSResources.Prefer_coalesce_expression, s_preferCoalesceExpression, s_preferCoalesceExpression, this, optionSet, nullCheckingGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, this, optionSet, nullCheckingGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferIsNullCheckOverReferenceEqualityMethod, CSharpVSResources.Prefer_is_null_over_ReferenceEquals, s_preferIsNullOverReferenceEquals, s_preferIsNullOverReferenceEquals, this, optionSet, nullCheckingGroupTitle)); + + // Field preferences. + CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly, s_preferReadonly, s_preferReadonly, this, optionSet, fieldGroupTitle)); } private void AddExpressionBodyOptions(OptionSet optionSet, string expressionPreferencesGroupTitle) diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs b/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs index 84888d4d3ff29a20d744a9debdc7c37b6cc6987e..bd8c83df668b65549d4abd82b17a430a9f8d5ef3 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs +++ b/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs @@ -934,6 +934,15 @@ internal class ServicesVSResources { } } + /// + /// Looks up a localized string similar to Field preferences:. + /// + internal static string Field_preferences_colon { + get { + return ResourceManager.GetString("Field_preferences_colon", resourceCulture); + } + } + /// /// Looks up a localized string similar to File already exists. /// @@ -1785,6 +1794,15 @@ internal class ServicesVSResources { } } + /// + /// Looks up a localized string similar to Prefer readonly. + /// + internal static string Prefer_readonly { + get { + return ResourceManager.GetString("Prefer_readonly", resourceCulture); + } + } + /// /// Looks up a localized string similar to Prefer simple 'default' expression. /// diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 5c0640e62d278e9abc52a41c79919b7b4b70966e..18bcfb2c157f028e70d0585f3613101e0270c2b1 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1022,4 +1022,10 @@ I agree to all of the foregoing: The settings configured here only apply to your machine. To configure these settings to travel with your solution, use .editorconfig files. + + Field preferences: + + + Prefer readonly + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 8bb505f23d458e80551f6005ebaf83054b49e7d2..7181d92c76d5b87e26e213568a1e4dcb8618541d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -1508,6 +1508,16 @@ Souhlasím se všemi výše uvedenými podmínkami: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 20af5e8e476f5ef0e7f9c20a6ec621a08829af51..568d8d007da89886f64e8bce9eb3c1d839ae124a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -1508,6 +1508,16 @@ Ich stimme allen vorstehenden Bedingungen zu: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 5799ab7b4e14dfc18d28b7e04d3789b6cd38d74c..ccad4cc423f6c1ff3cbd9527133b12d5a7024d97 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -1508,6 +1508,16 @@ Estoy de acuerdo con todo lo anterior: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index bee3766db382e13ec86011a3b0699eead78cebeb..462d861263b410799fa542112647df636ab7f766 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -1508,6 +1508,16 @@ Je suis d'accord avec tout ce qui précède : Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 3c1d2d37f1b2cafb0f26950be31d6fe3cc74ca5f..65b5095aa988561dacaa1d7aa472545267f9f06d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -1508,6 +1508,16 @@ L'utente accetta le condizioni sopra riportate: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index e41686050a83eb02436c9c32fa1944e8e71b1496..54f353b6b28099c527373d23346f1fbdad9b7e34 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -1508,6 +1508,16 @@ I agree to all of the foregoing: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 79088cb415c0b176aa559bcd68a3732998cde8fe..e4766ce18bc171ae65fe66f60178c654fb8e17a5 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -1508,6 +1508,16 @@ I agree to all of the foregoing: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 96d16174d252e469cb7e24bdb1a92d2d55f2097a..058c626156b76fae2411b7ddb586f7e05ece7b6d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -1508,6 +1508,16 @@ Wyrażam zgodę na wszystkie następujące postanowienia: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 02c33ef44a505cf1280df5ac52f56aeb06ca1f83..67d9ca3a1c686393e9e7d0f706f917ea4055d151 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -1508,6 +1508,16 @@ Eu concordo com todo o conteúdo supracitado: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 7ee92eae3b9b3616c03d30c50bf6baeb05fe712c..11c8a36b2bd2657ec965967aa201a5d1214eef64 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -1508,6 +1508,16 @@ I agree to all of the foregoing: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 2100458a376db915ee13709dd459129821cf7dd1..69fd46f660a0dd055e56b4d0f02c309e074a65e3 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -1508,6 +1508,16 @@ Aşağıdakilerin tümünü onaylıyorum: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index ff35c1241dc1c7db4d884c2d46c58862aa005e91..e864e40f83784285bbdd44b64e03e927d2acd948 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -1508,6 +1508,16 @@ I agree to all of the foregoing: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index bba87ac65c6d5a5c2b29fb8fd6027576b0abb434..b1e40d2ce5c2315b530ac6aac9416fff48a15f9a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -1508,6 +1508,16 @@ I agree to all of the foregoing: Sync Class View Command Handler + + Field preferences: + Field preferences: + + + + Prefer readonly + Prefer readonly + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb index e183e9bb0342dcf704ae68056f93f3c464251910..0358e505908b23514563636257960bae96750fb7 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb @@ -279,6 +279,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options End Set End Property + Public Property Style_PreferReadonly As String + Get + Return GetXmlOption(CodeStyleOptions.PreferReadonly) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions.PreferReadonly, value) + End Set + End Property + Public Property Option_PlaceSystemNamespaceFirst As Boolean Get Return GetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst) diff --git a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb index f700e7be34dd3b8ee2feff8c948b471a682e935c..7b3081c66bd8bbea61e02cf168487b8ecf0059de 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb @@ -310,6 +310,19 @@ Class Customer End Sub End Class" + Private Shared ReadOnly s_preferReadonly As String = $" +Class Customer +//[ + ' {ServicesVSResources.Prefer_colon} + ' 'value' can only be assigned in constructor + Private ReadOnly value As Integer = 0 + + ' {ServicesVSResources.Over_colon} + ' 'value' can be assigned anywhere + Private value As Integer = 0 +//] +End Class" + #End Region Public Sub New(optionSet As OptionSet, serviceProvider As IServiceProvider) @@ -335,6 +348,7 @@ End Class" Dim codeBlockPreferencesGroupTitle = ServicesVSResources.Code_block_preferences_colon Dim expressionPreferencesGroupTitle = ServicesVSResources.Expression_preferences_colon Dim nothingPreferencesGroupTitle = BasicVSResources.nothing_checking_colon + Dim fieldPreferencesGroupTitle = ServicesVSResources.Field_preferences_colon ' qualify with Me. group Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.QualifyFieldAccess, BasicVSResources.Qualify_field_access_with_Me, s_fieldDeclarationPreviewTrue, s_fieldDeclarationPreviewFalse, Me, optionSet, qualifyGroupTitle, qualifyMemberAccessPreferences)) @@ -360,6 +374,9 @@ End Class" Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferCoalesceExpression, ServicesVSResources.Prefer_coalesce_expression, s_preferCoalesceExpression, s_preferCoalesceExpression, Me, optionSet, nothingPreferencesGroupTitle)) Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, Me, optionSet, nothingPreferencesGroupTitle)) Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferIsNullCheckOverReferenceEqualityMethod, BasicVSResources.Prefer_Is_Nothing_over_ReferenceEquals, s_preferIsNothingCheckOverReferenceEquals, s_preferIsNothingCheckOverReferenceEquals, Me, optionSet, nothingPreferencesGroupTitle)) + + ' field preferences + Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly, s_preferReadonly, s_preferReadonly, Me, optionSet, fieldPreferencesGroupTitle)) End Sub End Class End Namespace diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index 47eb8b6e2a527edb6e794f9fce3b01b88ed99fab..206f27db5a395299467ed098e59b50cb1c1131d8 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -201,6 +201,11 @@ public bool IsAnonymousFunction(SyntaxNode node) node is AnonymousMethodExpressionSyntax; } + public bool IsLocalFunction(SyntaxNode node) + { + return node is LocalFunctionStatementSyntax; + } + public bool IsGenericName(SyntaxNode node) { return node is GenericNameSyntax; diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs index dda125309e83df8dd6e944102c187bc974ad24fa..8eb354b75825263a0d2eaedc61bef2b6b06e3c6d 100644 --- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs +++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs @@ -184,6 +184,14 @@ public class CodeStyleOptions new EditorConfigStorageLocation>("dotnet_style_require_accessibility_modifiers", s => ParseAccessibilityModifiersRequired(s)), new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.RequireAccessibilityModifiers")}); + internal static readonly PerLanguageOption> PreferReadonly = new PerLanguageOption>( + nameof(CodeStyleOptions), + nameof(PreferReadonly), + defaultValue: TrueWithSuggestionEnforcement, + storageLocations: new OptionStorageLocation[]{ + EditorConfigStorageLocation.ForBoolCodeStyleOption("dotnet_style_readonly_field"), + new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferReadonly") }); + private static CodeStyleOption ParseAccessibilityModifiersRequired(string optionString) { if (TryGetCodeStyleValueAndOptionalNotification(optionString, diff --git a/src/Workspaces/Core/Portable/Execution/AbstractOptionsSerializationService.cs b/src/Workspaces/Core/Portable/Execution/AbstractOptionsSerializationService.cs index 250b2fd21172d3abb781148b8d1e1526f0452abd..d7dda7e7f5bd7011e08d8b74119aa679d5b99998 100644 --- a/src/Workspaces/Core/Portable/Execution/AbstractOptionsSerializationService.cs +++ b/src/Workspaces/Core/Portable/Execution/AbstractOptionsSerializationService.cs @@ -237,6 +237,7 @@ protected void WriteOptionSetTo(OptionSet options, string language, ObjectWriter WriteOptionTo(options, language, SimplificationOptions.NamingPreferences, writer, cancellationToken); WriteOptionTo(options, language, CodeStyleOptions.PreferInferredTupleNames, writer, cancellationToken); WriteOptionTo(options, language, CodeStyleOptions.PreferInferredAnonymousTypeMemberNames, writer, cancellationToken); + WriteOptionTo(options, language, CodeStyleOptions.PreferReadonly, writer, cancellationToken); } protected OptionSet ReadOptionSetFrom(OptionSet options, string language, ObjectReader reader, CancellationToken cancellationToken) @@ -262,6 +263,7 @@ protected OptionSet ReadOptionSetFrom(OptionSet options, string language, Object options = ReadOptionFrom(options, language, SimplificationOptions.NamingPreferences, reader, cancellationToken); options = ReadOptionFrom(options, language, CodeStyleOptions.PreferInferredTupleNames, reader, cancellationToken); options = ReadOptionFrom(options, language, CodeStyleOptions.PreferInferredAnonymousTypeMemberNames, reader, cancellationToken); + options = ReadOptionFrom(options, language, CodeStyleOptions.PreferReadonly, reader, cancellationToken); return options; } diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index 672994e302671527a58b968c8abb055f231e4d8a..c698dea827d42352e9772bc9196f27c6c17b3091 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -255,6 +255,8 @@ internal interface ISyntaxFactsService : ILanguageService bool IsAnonymousFunction(SyntaxNode n); + bool IsLocalFunction(SyntaxNode n); + bool IsInConstantContext(SyntaxNode node); bool IsInConstructor(SyntaxNode node); bool IsMethodLevelMember(SyntaxNode node); diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 67e005316e595c4d7be893812e6cbfb8a8595561..ddc49937390d2c5fb568b758e39bdf40f182fa2d 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -201,6 +201,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return TypeOf node Is LambdaExpressionSyntax End Function + Public Function IsLocalFunction(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLocalFunction + Return False + End Function + Public Function IsGenericName(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsGenericName Return TypeOf node Is GenericNameSyntax End Function