未验证 提交 09bafa5a 编写于 作者: S Sam Harwell 提交者: GitHub

Merge pull request #19067 from Hosch250/master

Add Make Field Readonly analyzer/code fix
// 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<int> 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<int> 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<int, bool> {|FixAllInDocument:_test1|} = x => x > 0;
private static Func<int, bool> _test2 = x => x < 0;
private static Func<int, bool> _test3 = x =>
{
return x == 0;
};
private static Func<int, bool> _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<int, bool> {|FixAllInDocument:_test1|} = x => x > 0;
private static Func<int, bool> _test2 = x => x < 0;
private static Func<int, bool> _test3 = x =>
{
return x == 0;
};
private static Func<int, bool> _test4 = x =>
{
return x != 0;
};
}
partial struct MyClass { }",
@" partial struct MyClass
{
private static readonly Func<int, bool> _test1 = x => x > 0;
private static readonly Func<int, bool> _test2 = x => x < 0;
private static readonly Func<int, bool> _test3 = x =>
{
return x == 0;
};
private static readonly Func<int, bool> _test4 = x =>
{
return x != 0;
};
}
partial struct MyClass { }");
}
}
}
// 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<VariableDeclaratorSyntax, FieldDeclarationSyntax>
{
protected override SyntaxNode GetInitializerNode(VariableDeclaratorSyntax declaration)
=> declaration.Initializer?.Value;
protected override ImmutableList<VariableDeclaratorSyntax> GetVariableDeclarators(FieldDeclarationSyntax fieldDeclaration)
=> fieldDeclaration.Declaration.Variables.ToImmutableListOrEmpty();
}
}
// 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<IdentifierNameSyntax, ConstructorDeclarationSyntax>
{
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);
}
}
}
......@@ -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);
......
......@@ -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";
......
......@@ -251,6 +251,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Add readonly modifier.
/// </summary>
internal static string Add_readonly_modifier {
get {
return ResourceManager.GetString("Add_readonly_modifier", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add reference to &apos;{0}&apos;..
/// </summary>
......@@ -2007,6 +2016,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Make field readonly.
/// </summary>
internal static string Make_field_readonly {
get {
return ResourceManager.GetString("Make_field_readonly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Make method synchronous.
/// </summary>
......
......@@ -1346,4 +1346,10 @@ This version used in: {2}</value>
<data name="Warning_colon_Collection_may_be_modified_during_iteration" xml:space="preserve">
<value>Warning: Collection may be modified during iteration.</value>
</data>
</root>
\ No newline at end of file
<data name="Add_readonly_modifier" xml:space="preserve">
<value>Add readonly modifier</value>
</data>
<data name="Make_field_readonly" xml:space="preserve">
<value>Make field readonly</value>
</data>
</root>
// 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<TSymbolSyntax, TFieldDeclarationSyntax>
: SyntaxEditorBasedCodeFixProvider
where TSymbolSyntax : SyntaxNode
where TFieldDeclarationSyntax : SyntaxNode
{
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.MakeFieldReadonlyDiagnosticId);
protected abstract SyntaxNode GetInitializerNode(TSymbolSyntax declaration);
protected abstract ImmutableList<TSymbolSyntax> 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<Diagnostic> diagnostics,
CancellationToken cancellationToken)
{
var declarators = new List<TSymbolSyntax>();
foreach (var diagnostic in diagnostics)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var diagnosticSpan = diagnostic.Location.SourceSpan;
declarators.Add(root.FindNode(diagnosticSpan).FirstAncestorOrSelf<TSymbolSyntax>());
}
await MakeFieldReadonlyAsync(document, editor, declarators).ConfigureAwait(false);
}
private async Task MakeFieldReadonlyAsync(Document document, SyntaxEditor editor, List<TSymbolSyntax> declarators)
{
var declaratorsByField = declarators.GroupBy(g => g.FirstAncestorOrSelf<TFieldDeclarationSyntax>());
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<Diagnostic> diagnostics,
SyntaxEditor editor,
CancellationToken cancellationToken)
{
return FixWithEditorAsync(document, editor, diagnostics, cancellationToken);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument) :
base(FeaturesResources.Add_readonly_modifier, createChangedDocument)
{
}
}
}
}
// 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<TIdentifierNameSyntax, TConstructorDeclarationSyntax>
: 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<IFieldSymbol>.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<IFieldSymbol> 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<IFieldSymbol> 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<TConstructorDeclarationSyntax>(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<T>(SyntaxNode node, out T ctor) where T : SyntaxNode
{
ctor = node.FirstAncestorOrSelf<T>();
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;
}
}
}
......@@ -2005,6 +2005,16 @@ Tato verze se používá zde: {2}.</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Diese Version wird verwendet in: {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Esta versión se utiliza en: {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Version utilisée dans : {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Questa versione è usata {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ This version used in: {2}</source>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ This version used in: {2}</source>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Ta wersja jest używana wersja: {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Essa versão é usada no: {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ This version used in: {2}</source>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ Bu sürüm şurada kullanılır: {2}</target>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ This version used in: {2}</source>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -2005,6 +2005,16 @@ This version used in: {2}</source>
<target state="new">Warning: Collection may be modified during iteration.</target>
<note />
</trans-unit>
<trans-unit id="Add_readonly_modifier">
<source>Add readonly modifier</source>
<target state="new">Add readonly modifier</target>
<note />
</trans-unit>
<trans-unit id="Make_field_readonly">
<source>Make field readonly</source>
<target state="new">Make field readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
' 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
<ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.MakeFieldReadonly), [Shared]>
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
' 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
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
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
......@@ -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";
......
......@@ -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
......
......@@ -781,6 +781,21 @@ class List<T>
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)
......
......@@ -934,6 +934,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Field preferences:.
/// </summary>
internal static string Field_preferences_colon {
get {
return ResourceManager.GetString("Field_preferences_colon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to File already exists.
/// </summary>
......@@ -1785,6 +1794,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Prefer readonly.
/// </summary>
internal static string Prefer_readonly {
get {
return ResourceManager.GetString("Prefer_readonly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer simple &apos;default&apos; expression.
/// </summary>
......
......@@ -1022,4 +1022,10 @@ I agree to all of the foregoing:</value>
<data name="Code_style_header_use_editor_config" xml:space="preserve">
<value>The settings configured here only apply to your machine. To configure these settings to travel with your solution, use .editorconfig files.</value>
</data>
<data name="Field_preferences_colon" xml:space="preserve">
<value>Field preferences:</value>
</data>
<data name="Prefer_readonly" xml:space="preserve">
<value>Prefer readonly</value>
</data>
</root>
......@@ -1508,6 +1508,16 @@ Souhlasím se všemi výše uvedenými podmínkami:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ Ich stimme allen vorstehenden Bedingungen zu:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ Estoy de acuerdo con todo lo anterior:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ Je suis d'accord avec tout ce qui précède :</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ L'utente accetta le condizioni sopra riportate:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ I agree to all of the foregoing:</source>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ I agree to all of the foregoing:</source>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ Wyrażam zgodę na wszystkie następujące postanowienia:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ Eu concordo com todo o conteúdo supracitado:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ I agree to all of the foregoing:</source>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ Aşağıdakilerin tümünü onaylıyorum:</target>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ I agree to all of the foregoing:</source>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -1508,6 +1508,16 @@ I agree to all of the foregoing:</source>
<target state="new">Sync Class View Command Handler</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer readonly</source>
<target state="new">Prefer readonly</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -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)
......
......@@ -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
......@@ -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;
......
......@@ -184,6 +184,14 @@ public class CodeStyleOptions
new EditorConfigStorageLocation<CodeStyleOption<AccessibilityModifiersRequired>>("dotnet_style_require_accessibility_modifiers", s => ParseAccessibilityModifiersRequired(s)),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.RequireAccessibilityModifiers")});
internal static readonly PerLanguageOption<CodeStyleOption<bool>> PreferReadonly = new PerLanguageOption<CodeStyleOption<bool>>(
nameof(CodeStyleOptions),
nameof(PreferReadonly),
defaultValue: TrueWithSuggestionEnforcement,
storageLocations: new OptionStorageLocation[]{
EditorConfigStorageLocation.ForBoolCodeStyleOption("dotnet_style_readonly_field"),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferReadonly") });
private static CodeStyleOption<AccessibilityModifiersRequired> ParseAccessibilityModifiersRequired(string optionString)
{
if (TryGetCodeStyleValueAndOptionalNotification(optionString,
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册