Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(3/4) Use Span-Based String Concat Analyzer and Fixer #4764

Merged

Conversation

NewellClark
Copy link
Contributor

@NewellClark NewellClark commented Feb 2, 2021

Fixes issue #33777.
Issues in dotnet/runtime have been addressed.

@NewellClark NewellClark requested a review from a team as a code owner February 2, 2021 17:10
@NewellClark NewellClark marked this pull request as draft February 2, 2021 17:12
NewellClark and others added 2 commits February 2, 2021 12:13
…CoreAnalyzersResources.resx


Fix title

Co-authored-by: Manish Vasani <mavasani@microsoft.com>
…CoreAnalyzersResources.resx

Co-authored-by: Manish Vasani <mavasani@microsoft.com>
Comment on lines +78 to +87
public Task SingleViolationInOneBlock_ReportedAndFixed_CS(string testStatements, string fixedStatements)
{
var test = new VerifyCS.Test
{
TestCode = CSUsings + CSWithBody(testStatements),
FixedCode = CSUsings + CSWithBody(fixedStatements),
ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) }
};
return test.RunAsync();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making the tests async, then await test.RunAsync().

Copy link
Contributor Author

@NewellClark NewellClark Feb 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public async Task TestMethod() => await test.RunAsync(); is semantically identical to
public Task TestMethod() => return test.RunAsync();, but potentially slower. If we needed to await multiple async calls, it would make sense to mark the method as async, but since we have just that one async call, it makes sense to simply return the Task produced by test.RunAsync().

Basically, we already have a Task. May as well just return it.

Copy link
Member

@Youssef1313 Youssef1313 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider having tests against null-conditional access operator (?.).

@NewellClark
Copy link
Contributor Author

NewellClark commented Feb 3, 2021

Consider having tests against null-conditional access operator (?.).

@Youssef1313 Do you mean for Substring invocations, as in foo?.Substring(1) + bar?

@Youssef1313
Copy link
Member

Consider having tests against null-conditional access operator (?.).

@Youssef1313 Do you mean for Substring invocations, as in foo?.Substring(1) + bar?

Yes.

@NewellClark
Copy link
Contributor Author

Consider having tests against null-conditional access operator (?.).

@Youssef1313 Do you mean for Substring invocations, as in foo?.Substring(1) + bar?

Yes.

What should the fixer do for foo?.Substring(1)? Because the span-based equivalent is something like
foo is null ? default : foo.AsSpan(1), which seems kind of verbose for the fixer to be inserting. Should we ignore cases where Substring is accessed conditionally?

@Youssef1313
Copy link
Member

Consider having tests against null-conditional access operator (?.).

@Youssef1313 Do you mean for Substring invocations, as in foo?.Substring(1) + bar?

Yes.

What should the fixer do for foo?.Substring(1)? Because the span-based equivalent is something like
foo is null ? default : foo.AsSpan(1), which seems kind of verbose for the fixer to be inserting. Should we ignore cases where Substring is accessed conditionally?

I'm not sure whether the fixer should do something or not. But I think at least the analyzer should flag these.

@mavasani @bartonjs What do you think?

@bartonjs
Copy link
Member

bartonjs commented Feb 3, 2021

I'm not sure whether the fixer should do something or not. But I think at least the analyzer should flag these.

That's a reasonable starting position.

…d made it a static method.

- Added tests for conditional substring invocation. We report conditional substring invocations, but do not fix them. 
- Fixed issue where `Imports System` was unnecessarily being added in projects that had `System` as an implicit global import, and added tests for VB projects with and without an implicit global `System` import. 
- Added null tests for both string and char type symbols, just in case. 
- Used exact values for expected iteration counts for nested violations tests. 
- Other minor style changes.
Some tests didn't explicitly specify net5.0, which caused the tests to fail due to lack of span-based string.Concat.
@NewellClark
Copy link
Contributor Author

I've made the requested changes.

The fixer will now only add Imports System in VB if it is not specified as an implicit global import. System is imported by default in projects created in Visual Studio, but it can be toggled off, so I've added tests ensuring the fixer always does the right thing regardless of user configs.

Analyzer detects foo?.Substring(1) + whatever but fixer won't fix it, as previously discussed.

@codecov
Copy link

codecov bot commented Feb 4, 2021

Codecov Report

Merging #4764 (092b95d) into release/6.0.1xx (d515202) will increase coverage by 0.00%.
The diff coverage is 93.62%.

@@                Coverage Diff                 @@
##           release/6.0.1xx    #4764     +/-   ##
==================================================
  Coverage            95.55%   95.56%             
==================================================
  Files                 1203     1210      +7     
  Lines               274337   275763   +1426     
  Branches             16632    16713     +81     
==================================================
+ Hits                262137   263526   +1389     
- Misses                9988    10005     +17     
- Partials              2212     2232     +20     

@NewellClark
Copy link
Contributor Author

How do we want to handle character arguments? I was thinking stackalloc char[] { charVar }, but that could potentially run into CA2014 if the concatenation is inside a loop.

// If the current operation is a string-concatenation operation, walk up to the top-most concat
// operation and add it to the set.
var binary = (IBinaryOperation)context.Operation;
if (!TryGetTopMostConcatOperation(binary, out var topMostConcatOperation))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach seems fine, but seems you can do without topMostConcatOperations and reporting in the end action. Instead you can just have the following in IBinaryOperation callback:

  if (IsTopMostConcatOperation(binary) && ShouldBeReported(binary))
  {
      context.ReportDiagnostic(binary.CreateDiagnostic(Rule));
  }

Copy link
Contributor Author

@NewellClark NewellClark Apr 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this approach is that we may end up reporting the same violation multiple times. I remember encountering this issue while writing the analyzer. I'll add a comment clarifying this.

}
}

internal static ImmutableArray<IOperation> FlattenBinaryOperation(IBinaryOperation root)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be much simpler to just write a specialized OperationWalker here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I've done that.

Comment on lines 192 to 193
case LanguageNames.CSharp when conversion.Type.SpecialType is SpecialType.System_Object:
case LanguageNames.VisualBasic when conversion.Type.SpecialType is SpecialType.System_Object or SpecialType.System_String:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move all language specific logic to language specific layers instead of checking language names - you already have similar abstract methods and language specific operation walking code, so please move this out as well.

Copy link
Contributor Author

@NewellClark NewellClark Apr 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I did have to add two static methods with the VB and CS implementations, as the method is called from both the analyzer and the fixer, but I've taken care of it.

if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode concatExpressionSyntax)
return;
// OperatorKind will be BinaryOperatorKind.Concatenate, even when '+' is used instead of '&' in Visual Basic.
if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind is not (BinaryOperatorKind.Add or BinaryOperatorKind.Concatenate))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please break down the expression into separate lines for readability

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Contributor

@mavasani mavasani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. Thanks for extensive tests.

NewellClark and others added 2 commits April 23, 2021 15:07
Co-authored-by: Manish Vasani <mavasani@microsoft.com>
- Apply Mavasani's changes
- Remove unused method from fixers
@NewellClark NewellClark changed the base branch from release/6.0.1xx-preview1 to release/6.0.1xx-preview4 April 24, 2021 17:36
@NewellClark NewellClark changed the base branch from release/6.0.1xx-preview4 to release/6.0.1xx-preview1 April 24, 2021 17:59
@NewellClark NewellClark changed the base branch from release/6.0.1xx-preview1 to release/6.0.1xx-preview4 April 24, 2021 18:13
Comment on lines +114 to +118
if (!IsSystemNamespaceImported(context.Document.Project, generator.GetNamespaceImports(newRoot)))
{
SyntaxNode systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System));
newRoot = generator.AddNamespaceImports(newRoot, systemNamespaceImport);
}
Copy link
Member

@Youssef1313 Youssef1313 Apr 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to check if calling WithAddImportsAnnotation on concatMethodInvocationSyntax eliminate the need of IsSystemNamespaceImported?

If WithAddImportsAnnotation didn't work, consider doing a case-insensitive string comparison in the VB override.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithAddImportsAnnotation only works for syntax nodes that represent types (just tested it).
I did change the VB fixer to use a case-insensitive comparison (good catch).

Use case-insensitive string comparison to import System namespace in the VB fixer.
@mavasani
Copy link
Contributor

Same request and apologies as #4726 (comment). This needs to be targeted to release\6.0.1xx. Thanks!

Co-authored-by: Manish Vasani <mavasani@microsoft.com>
@NewellClark NewellClark changed the base branch from release/6.0.1xx-preview4 to release/6.0.1xx May 17, 2021 15:48
@mavasani mavasani merged commit 0f43809 into dotnet:release/6.0.1xx May 18, 2021
@NewellClark NewellClark deleted the use-span-based-concat-analyzer branch August 27, 2021 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants