Skip to content

Commit

Permalink
Merge branch 'release/6.0.1xx' into prefer-AsSpan-over-Substring-anal…
Browse files Browse the repository at this point in the history
…yzer
  • Loading branch information
NewellClark committed May 20, 2021
2 parents 34274f9 + 92a247f commit 34dc2d6
Show file tree
Hide file tree
Showing 37 changed files with 1,643 additions and 17 deletions.
4 changes: 2 additions & 2 deletions docs/NetCore_GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
8. In case no any failure introduce an error somewhere to prove that the rule ran.
- Be careful about in which project you are producing an error, choose an API not having reference from other APIs, else all dependent API's will fail.
9. If failures found, repeat step 4-5 to evaluate and address all warnings.
- In case you want to [debug some failures](#Debugging-analyzer-with-runtiem-repo-projects).
- In case you want to [debug some failures](#debugging-analyzer-with-runtime-repo-projects).

## Testing against the Roslyn repo

Expand All @@ -75,7 +75,7 @@ The diagnostics reported by the analyzer will be listed in Output.txt.

## Debugging analyzer with runtime repo projects

1. Copy over debug build of analyzer assemblies on top of NetAnalyzers nuget package in your packages folder. (Instructions are same as the step 1 and 2 of [Testing against the Runtime repo step](#Testing-against-the-Runtime-and-Roslyn-analyzers-repo))
1. Copy over debug build of analyzer assemblies on top of NetAnalyzers nuget package in your packages folder. (Instructions are same as the step 1 and 2 of [Testing against the Runtime repo step](#testing-against-the-runtime-and-roslyn-analyzers-repo))
2. Start VS and open a project you want to debug
3. Note the process ID for `ServiceHub.RoslynCodeAnalysisService.exe` corresponding to that VS instance
- If you are using `Visual Studio` older than version `16.8 Preview2` then analyzers run in `devenv.exe`, you will need to attach that process instead
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<VersionPrefix>3.3.3</VersionPrefix>
<PreReleaseVersionLabel>beta1</PreReleaseVersionLabel>
<NetAnalyzersVersionPrefix>6.0.0</NetAnalyzersVersionPrefix>
<NetAnalyzersPreReleaseVersionLabel>preview5</NetAnalyzersPreReleaseVersionLabel>
<NetAnalyzersPreReleaseVersionLabel>preview6</NetAnalyzersPreReleaseVersionLabel>
<AnalyzerUtilitiesVersionPrefix>$(VersionPrefix)</AnalyzerUtilitiesVersionPrefix>
<!--
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
Expand Down
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ CA1843 | Performance | Info | DoNotUseWhenAllOrWaitAllWithSingleArgument, [Docum
CA1844 | Performance | Info | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1844)
CA1845 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845)
CA1846 | Performance | Info | PreferAsSpanOverSubstring, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1846)
CA2250 | Usage | Info | UseCancellationTokenThrowIfCancellationRequested, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250)

### Removed Rules

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public override void Initialize(AnalysisContext context)
return;
}
if (!symbol.IsProtected() ||
if (!IsAnyProtectedVariant(symbol) ||
symbol.IsOverride ||
!symbol.ContainingType.IsSealed)
{
Expand All @@ -64,5 +64,12 @@ public override void Initialize(AnalysisContext context)
context.ReportDiagnostic(symbol.CreateDiagnostic(Rule, symbol.Name, symbol.ContainingType.Name));
}, SymbolKind.Method, SymbolKind.Property, SymbolKind.Event, SymbolKind.Field);
}

private static bool IsAnyProtectedVariant(ISymbol symbol)
{
return symbol.DeclaredAccessibility == Accessibility.Protected ||
symbol.DeclaredAccessibility == Accessibility.ProtectedOrInternal ||
symbol.DeclaredAccessibility == Accessibility.ProtectedAndInternal;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private static List<MethodCategory> GetMethodCategories(Compilation compilation)
new MethodCategory(IsEqualsOverrideOrInterfaceImplementation, true,
NoAllowedExceptionsRule),

new MethodCategory(IsEqualityOperator, true,
new MethodCategory(IsComparisonOperator, true,
NoAllowedExceptionsRule),

new MethodCategory(IsGetHashCodeOverride, true,
Expand Down Expand Up @@ -316,15 +316,19 @@ private static bool IsFinalizer(IMethodSymbol method, Compilation compilation)
return method.IsFinalizer();
}

private static bool IsEqualityOperator(IMethodSymbol method, Compilation compilation)
private static bool IsComparisonOperator(IMethodSymbol method, Compilation compilation)
{
if (!method.IsStatic || !method.IsPublic())
return false;

return method.Name switch
{
WellKnownMemberNames.EqualityOperatorName
or WellKnownMemberNames.InequalityOperatorName => true,
or WellKnownMemberNames.InequalityOperatorName
or WellKnownMemberNames.LessThanOperatorName
or WellKnownMemberNames.GreaterThanOperatorName
or WellKnownMemberNames.LessThanOrEqualOperatorName
or WellKnownMemberNames.GreaterThanOrEqualOperatorName => true,
_ => false,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,18 @@
<data name="PreferAsSpanOverSubstringCodefixTitle" xml:space="preserve">
<value>Replace 'Substring' with 'AsSpan'</value>
</data>
<data name="UseCancellationTokenThrowIfCancellationRequestedDescription" xml:space="preserve">
<value>'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has.</value>
</data>
<data name="UseCancellationTokenThrowIfCancellationRequestedMessage" xml:space="preserve">
<value>Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException'</value>
</data>
<data name="UseCancellationTokenThrowIfCancellationRequestedTitle" xml:space="preserve">
<value>Use 'ThrowIfCancellationRequested'</value>
</data>
<data name="UseCancellationTokenThrowIfCancellationRequestedCodeFixTitle" xml:space="preserve">
<value>Replace with 'CancellationToken.ThrowIfCancellationRequested'</value>
</data>
<data name="ProvideStreamMemoryBasedAsyncOverridesDescription" xml:space="preserve">
<value>To improve performance, override the memory-based async methods when subclassing 'Stream'. Then implement the array-based methods in terms of the memory-based methods.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// 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.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Operations;
using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseCancellationTokenThrowIfCancellationRequested.RequiredSymbols;
using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources;

namespace Microsoft.NetCore.Analyzers.Runtime
{
/// <summary>
/// Use <see cref="CancellationToken.ThrowIfCancellationRequested"/> instead of checking <see cref="CancellationToken.IsCancellationRequested"/> and
/// throwing <see cref="OperationCanceledException"/>.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
public sealed class UseCancellationTokenThrowIfCancellationRequestedFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(UseCancellationTokenThrowIfCancellationRequested.RuleId);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SemanticModel model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
if (!RequiredSymbols.TryGetSymbols(model.Compilation, out RequiredSymbols symbols))
return;
SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
SyntaxNode node = root.FindNode(context.Span);
if (model.GetOperation(node, context.CancellationToken) is not IConditionalOperation conditional)
return;

Func<CancellationToken, Task<Document>> createChangedDocument;
if (symbols.IsSimpleAffirmativeCheck(conditional, out IPropertyReferenceOperation? propertyReference))
{
// For simple checks of the form:
// if (token.IsCancellationRequested)
// throw new OperationCanceledException();
// Replace with:
// token.ThrowIfCancellationRequested();
//
// For simple checks of the form:
// if (token.IsCancellationRequested)
// throw new OperationCanceledException();
// else
// Frob();
// Replace with:
// token.ThrowIfCancellationRequested();
// Frob();
createChangedDocument = async token =>
{
var editor = await DocumentEditor.CreateAsync(context.Document, token).ConfigureAwait(false);
SyntaxNode expressionStatement = CreateThrowIfCancellationRequestedExpressionStatement(editor, conditional, propertyReference);
editor.ReplaceNode(conditional.Syntax, expressionStatement);
if (conditional.WhenFalse is IBlockOperation block)
{
editor.InsertAfter(expressionStatement, block.Operations.Select(x => x.Syntax.WithAdditionalAnnotations(Formatter.Annotation)));
}
else if (conditional.WhenFalse is not null)
{
editor.InsertAfter(expressionStatement, conditional.WhenFalse.Syntax);
}
return editor.GetChangedDocument();
};
}
else if (symbols.IsNegatedCheckWithThrowingElseClause(conditional, out propertyReference))
{
// For negated checks of the form:
// if (!token.IsCancellationRequested) { DoStatements(); }
// else { throw new OperationCanceledException(); }
// Replace with:
// token.ThrowIfCancellationRequested();
// DoStatements();
createChangedDocument = async token =>
{
var editor = await DocumentEditor.CreateAsync(context.Document, token).ConfigureAwait(false);
SyntaxNode expressionStatement = CreateThrowIfCancellationRequestedExpressionStatement(editor, conditional, propertyReference)
.WithAdditionalAnnotations(Formatter.Annotation);
editor.ReplaceNode(conditional.Syntax, expressionStatement);
if (conditional.WhenTrue is IBlockOperation block)
{
editor.InsertAfter(expressionStatement, block.Operations.Select(x => x.Syntax.WithAdditionalAnnotations(Formatter.Annotation)));
}
else
{
editor.InsertAfter(expressionStatement, conditional.WhenTrue.Syntax);
}
return editor.GetChangedDocument();
};
}
else
{
return;
}

var codeAction = CodeAction.Create(
Resx.UseCancellationTokenThrowIfCancellationRequestedCodeFixTitle,
createChangedDocument,
Resx.UseCancellationTokenThrowIfCancellationRequestedCodeFixTitle);
context.RegisterCodeFix(codeAction, context.Diagnostics);
}

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

private static SyntaxNode CreateThrowIfCancellationRequestedExpressionStatement(
DocumentEditor editor,
IConditionalOperation conditional,
IPropertyReferenceOperation isCancellationRequestedPropertyReference)
{
SyntaxNode memberAccess = editor.Generator.MemberAccessExpression(
isCancellationRequestedPropertyReference.Instance.Syntax,
nameof(CancellationToken.ThrowIfCancellationRequested));
SyntaxNode invocation = editor.Generator.InvocationExpression(memberAccess, Array.Empty<SyntaxNode>());
var firstWhenTrueStatement = conditional.WhenTrue is IBlockOperation block ? block.Operations.FirstOrDefault() : conditional.WhenTrue;

var result = editor.Generator.ExpressionStatement(invocation);
result = firstWhenTrueStatement is not null ? result.WithTriviaFrom(firstWhenTrueStatement.Syntax) : result;
return result.WithAdditionalAnnotations(Formatter.Annotation);
}
}
}
Loading

0 comments on commit 34dc2d6

Please sign in to comment.