Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
36 changes: 0 additions & 36 deletions src/tools/illink/.editorconfig
Original file line number Diff line number Diff line change
@@ -1,37 +1,4 @@
[*.cs]
indent_style = tab
indent_size = 4
csharp_new_line_before_open_brace = types,methods
csharp_new_line_before_else = false
csharp_new_line_before_catch = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_declaration_name_and_open_parenthesis = true
csharp_space_between_method_call_name_and_opening_parenthesis = true
csharp_space_before_open_square_brackets = false
csharp_space_after_cast = true

csharp_indent_switch_labels = false

# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true

# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none

# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# Avoid redundant accessibility modifiers when they're default
dotnet_style_require_accessibility_modifiers = omit_if_default:suggestion

Expand All @@ -42,9 +9,6 @@ file_header_template = Copyright (c) .NET Foundation and contributors. All right
# Spacing around keywords
dotnet_diagnostic.SA1000.severity = none

# Closing generic bracket should not be followed by a space
dotnet_diagnostic.SA1015.severity = none

# Access modifier must be declared
dotnet_diagnostic.SA1400.severity = none

Expand Down
261 changes: 133 additions & 128 deletions src/tools/illink/src/ILLink.CodeFix/BaseAttributeCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,132 +16,137 @@

namespace ILLink.CodeFix
{
public abstract class BaseAttributeCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider
{
private protected abstract LocalizableString CodeFixTitle { get; }

private protected abstract string FullyQualifiedAttributeName { get; }

private protected abstract AttributeableParentTargets AttributableParentTargets { get; }

public sealed override FixAllProvider GetFixAllProvider ()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}

protected async Task BaseRegisterCodeFixesAsync (CodeFixContext context)
{
var document = context.Document;
var diagnostic = context.Diagnostics.First ();
var codeFixTitle = CodeFixTitle.ToString ();

if (await document.GetSyntaxRootAsync (context.CancellationToken).ConfigureAwait (false) is not { } root)
return;

SyntaxNode targetNode = root.FindNode (diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
if (FindAttributableParent (targetNode, AttributableParentTargets) is not SyntaxNode attributableNode)
return;

context.RegisterCodeFix (CodeAction.Create (
title: codeFixTitle,
createChangedDocument: ct => AddAttributeAsync (
document, diagnostic, targetNode, attributableNode, ct),
equivalenceKey: codeFixTitle), diagnostic);
}

private async Task<Document> AddAttributeAsync (
Document document,
Diagnostic diagnostic,
SyntaxNode targetNode,
SyntaxNode attributableNode,
CancellationToken cancellationToken)
{
if (await document.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false) is not { } model)
return document;
if (model.GetSymbolInfo (targetNode, cancellationToken).Symbol is not { } targetSymbol)
return document;
if (model.Compilation.GetBestTypeByMetadataName (FullyQualifiedAttributeName) is not { } attributeSymbol)
return document;

// N.B. May be null for FieldDeclaration, since field declarations can declare multiple variables
var attributableSymbol = model.GetDeclaredSymbol (attributableNode, cancellationToken);

var attributeArguments = GetAttributeArguments (attributableSymbol, targetSymbol, SyntaxGenerator.GetGenerator (document), diagnostic);

var editor = await DocumentEditor.CreateAsync (document, cancellationToken).ConfigureAwait (false);
var generator = editor.Generator;
var attribute = generator.Attribute (
generator.TypeExpression (attributeSymbol), attributeArguments)
.WithAdditionalAnnotations (Simplifier.Annotation, Simplifier.AddImportsAnnotation);

editor.AddAttribute (attributableNode, attribute);
return editor.GetChangedDocument ();
}

[Flags]
protected enum AttributeableParentTargets
{
MethodOrConstructor = 0x0001,
Property = 0x0002,
Field = 0x0004,
Event = 0x0008,
Class = 0x0010,
All = MethodOrConstructor | Property | Field | Event | Class
}

private static CSharpSyntaxNode? FindAttributableParent (SyntaxNode node, AttributeableParentTargets targets)
{
SyntaxNode? parentNode = node.Parent;
while (parentNode is not null) {
switch (parentNode) {
case LambdaExpressionSyntax:
return null;

case PropertyDeclarationSyntax when targets.HasFlag (AttributeableParentTargets.Property):
case EventDeclarationSyntax when targets.HasFlag (AttributeableParentTargets.Event):
return (CSharpSyntaxNode) parentNode;
case PropertyDeclarationSyntax:
case EventDeclarationSyntax:
// If the attribute can be placed on a method but not directly on a property/event, we don't want to keep walking up
// the syntax tree to annotate the class. Instead the correct thing to do is to add accessor methods and annotate those.
// The code fixer doesn't support doing this automatically, so return null to indicate that the attribute can't be added.
if (targets.HasFlag (AttributeableParentTargets.MethodOrConstructor))
return null;

parentNode = parentNode.Parent;
break;
case LocalFunctionStatementSyntax or BaseMethodDeclarationSyntax or AccessorDeclarationSyntax when targets.HasFlag (AttributeableParentTargets.MethodOrConstructor):
case FieldDeclarationSyntax when targets.HasFlag (AttributeableParentTargets.Field):
case ClassDeclarationSyntax when targets.HasFlag (AttributeableParentTargets.Class):
return (CSharpSyntaxNode) parentNode;

default:
parentNode = parentNode.Parent;
break;
}
}

return null;
}

protected abstract SyntaxNode[] GetAttributeArguments (
ISymbol? attributableSymbol,
ISymbol targetSymbol,
SyntaxGenerator syntaxGenerator,
Diagnostic diagnostic);

protected static bool HasPublicAccessibility (ISymbol? m)
{
if (m is not { DeclaredAccessibility: Accessibility.Public or Accessibility.Protected }) {
return false;
}
for (var t = m.ContainingType; t is not null; t = t.ContainingType) {
if (t.DeclaredAccessibility != Accessibility.Public) {
return false;
}
}
return true;
}
}
public abstract class BaseAttributeCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider
{
private protected abstract LocalizableString CodeFixTitle { get; }

private protected abstract string FullyQualifiedAttributeName { get; }

private protected abstract AttributeableParentTargets AttributableParentTargets { get; }

public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}

protected async Task BaseRegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var diagnostic = context.Diagnostics.First();
var codeFixTitle = CodeFixTitle.ToString();

if (await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root)
return;

SyntaxNode targetNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
if (FindAttributableParent(targetNode, AttributableParentTargets) is not SyntaxNode attributableNode)
return;

context.RegisterCodeFix(CodeAction.Create(
title: codeFixTitle,
createChangedDocument: ct => AddAttributeAsync(
document, diagnostic, targetNode, attributableNode, ct),
equivalenceKey: codeFixTitle), diagnostic);
}

private async Task<Document> AddAttributeAsync(
Document document,
Diagnostic diagnostic,
SyntaxNode targetNode,
SyntaxNode attributableNode,
CancellationToken cancellationToken)
{
if (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false) is not { } model)
return document;
if (model.GetSymbolInfo(targetNode, cancellationToken).Symbol is not { } targetSymbol)
return document;
if (model.Compilation.GetBestTypeByMetadataName(FullyQualifiedAttributeName) is not { } attributeSymbol)
return document;

// N.B. May be null for FieldDeclaration, since field declarations can declare multiple variables
var attributableSymbol = model.GetDeclaredSymbol(attributableNode, cancellationToken);

var attributeArguments = GetAttributeArguments(attributableSymbol, targetSymbol, SyntaxGenerator.GetGenerator(document), diagnostic);

var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var generator = editor.Generator;
var attribute = generator.Attribute(
generator.TypeExpression(attributeSymbol), attributeArguments)
.WithAdditionalAnnotations(Simplifier.Annotation, Simplifier.AddImportsAnnotation);

editor.AddAttribute(attributableNode, attribute);
return editor.GetChangedDocument();
}

[Flags]
protected enum AttributeableParentTargets
{
MethodOrConstructor = 0x0001,
Property = 0x0002,
Field = 0x0004,
Event = 0x0008,
Class = 0x0010,
All = MethodOrConstructor | Property | Field | Event | Class
}

private static CSharpSyntaxNode? FindAttributableParent(SyntaxNode node, AttributeableParentTargets targets)
{
SyntaxNode? parentNode = node.Parent;
while (parentNode is not null)
{
switch (parentNode)
{
case LambdaExpressionSyntax:
return null;

case PropertyDeclarationSyntax when targets.HasFlag(AttributeableParentTargets.Property):
case EventDeclarationSyntax when targets.HasFlag(AttributeableParentTargets.Event):
return (CSharpSyntaxNode)parentNode;
case PropertyDeclarationSyntax:
case EventDeclarationSyntax:
// If the attribute can be placed on a method but not directly on a property/event, we don't want to keep walking up
// the syntax tree to annotate the class. Instead the correct thing to do is to add accessor methods and annotate those.
// The code fixer doesn't support doing this automatically, so return null to indicate that the attribute can't be added.
if (targets.HasFlag(AttributeableParentTargets.MethodOrConstructor))
return null;

parentNode = parentNode.Parent;
break;
case LocalFunctionStatementSyntax or BaseMethodDeclarationSyntax or AccessorDeclarationSyntax when targets.HasFlag(AttributeableParentTargets.MethodOrConstructor):
case FieldDeclarationSyntax when targets.HasFlag(AttributeableParentTargets.Field):
case ClassDeclarationSyntax when targets.HasFlag(AttributeableParentTargets.Class):
return (CSharpSyntaxNode)parentNode;

default:
parentNode = parentNode.Parent;
break;
}
}

return null;
}

protected abstract SyntaxNode[] GetAttributeArguments(
ISymbol? attributableSymbol,
ISymbol targetSymbol,
SyntaxGenerator syntaxGenerator,
Diagnostic diagnostic);

protected static bool HasPublicAccessibility(ISymbol? m)
{
if (m is not { DeclaredAccessibility: Accessibility.Public or Accessibility.Protected })
{
return false;
}
for (var t = m.ContainingType; t is not null; t = t.ContainingType)
{
if (t.DeclaredAccessibility != Accessibility.Public)
{
return false;
}
}
return true;
}
}
}
Loading
Loading