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

Fix S2234 FN: primary constructors for records, classes and structs #8238

Merged
merged 29 commits into from
Oct 25, 2023

Conversation

martin-strecker-sonarsource
Copy link
Contributor

@martin-strecker-sonarsource martin-strecker-sonarsource commented Oct 20, 2023

Fixes #8070
Fixes #8071

The original implementation was rewritten from scratch. The reasons are

  • The implementation didn't follow our principals
  • The analyzer wasn't derived from SonarDiagnosticAnalyzer<TSyntaxKind>
  • The registration happened in the derived classes instead of the base class
  • LanguageFacade wasn't used
  • There was a mixture of abstract methods (e.g., GetArgumentTypeSymbolInfo) and passed delegates (e.g., getLocationToReport parameter in ReportIncorrectlyOrderedParameters) which are both used as language-specific holes for the language-neutral implementation
  • The ArgumentIdentifier private class and derived classes are not needed
  • The logic was complex to follow

The current implementation follows our standards more closely and moves a lot of logic to the facade.

&& Language.Syntax.NodeExpression(argument) is { } argumentExpression
&& c.Context.SemanticModel.GetTypeInfo(argumentExpression).ConvertedType is { } argumentType
// is there another parameter that seems to be a better fit (name and type match): p_x
&& methodParameterLookup.MethodSymbol.Parameters.FirstOrDefault(p => MatchingNames(p, argumentName) && argumentType.DerivesOrImplements(p.Type)) is { IsParams: false }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

DerivesOrImplements misses a whole lot of conversions:
Identity conversions
Implicit numeric conversions
Implicit enumeration conversions
Implicit interpolated string conversions
Implicit reference conversions
Boxing conversions
Implicit dynamic conversions
Implicit type parameter conversions
Implicit constant expression conversions
User-defined implicit conversions
Anonymous function conversions
Method group conversions
Null literal conversions
Implicit nullable conversions
Implicit tuple conversions
Lifted user-defined implicit conversions
Default literal conversions
Implicit throw conversion

The best way to handle this is by using ClassifyConversion(SemanticModel, ExpressionSyntax, ITypeSymbol, Boolean) but this requires even more changes. I would propose to create an issue about adding something like ILanguageFace.IsAssignableTo(SyntaxNode expression, ITypeSymbol destination) to the facade, bring it under test, and use it here. We also should have a look at how Roslyn handles this.

=> node.GetLocation();

private static bool MatchingNames(IParameterSymbol parameter, string argumentName) =>
parameter.Name == argumentName;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ArgumentName() already returns names for all kinds of expressions:

  • M(A.B) -> B
  • M(A?.B) -> B
  • M(A())-> A
  • M(A) -> A (this might be unsupported at the moment)

and more could be added when this is fixed: #8241

Given that, the comparison should be replaced by StringComparison.OrdinalIgnoreCase for all languages.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a lot better!

analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs Outdated Show resolved Hide resolved
&& Language.Syntax.NodeExpression(argument) is { } argumentExpression
&& c.Context.SemanticModel.GetTypeInfo(argumentExpression).ConvertedType is { } argumentType
// is there another parameter that seems to be a better fit (name and type match): p_x
&& methodParameterLookup.MethodSymbol.Parameters.FirstOrDefault(p => MatchingNames(p, argumentName) && argumentType.DerivesOrImplements(p.Type)) is { IsParams: false }

Choose a reason for hiding this comment

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

Suggested change
&& methodParameterLookup.MethodSymbol.Parameters.FirstOrDefault(p => MatchingNames(p, argumentName) && argumentType.DerivesOrImplements(p.Type)) is { IsParams: false }
&& methodParameterLookup.MethodSymbol.Parameters.FirstOrDefault(p => MatchingNames(p, argumentName)
&& argumentType.DerivesOrImplements(p.Type)) is { IsParams: false }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor

Choose a reason for hiding this comment

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

you inlined that AND condition, all the others are listed one after the other

Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM! Please fix the last unresolved comment before merging

@martin-strecker-sonarsource
Copy link
Contributor Author

Can you please have a second look? I used the enhanced "NodeIdentifier" method of the facade for the primary location in CS:
0ebf903

and here I fixed the outstanding issue:
a9fe51c

@sonarcloud
Copy link

sonarcloud bot commented Oct 25, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@sonarcloud
Copy link

sonarcloud bot commented Oct 25, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

98.3% 98.3% Coverage
0.0% 0.0% Duplication

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good! I think you can merge this one

@martin-strecker-sonarsource martin-strecker-sonarsource merged commit 13e5548 into master Oct 25, 2023
27 checks passed
@martin-strecker-sonarsource martin-strecker-sonarsource deleted the Martin/CS12_S2234 branch October 25, 2023 14:54
@martin-strecker-sonarsource
Copy link
Contributor Author

martin-strecker-sonarsource commented Oct 26, 2023

Peach validation:

Lost one issue, which was an invocation of Func<string, object, object, bool> with (name, arg1, arg2). The delegate is defined as public delegate TResult Func<in T1,in T2,in T3,out TResult>(T1 arg1, T2 arg2, T3 arg3); and therefore this seems to be a FN. On the other hand arg1, .. are generic names of the Func delegate so in this case I would say it is a TN.
This might not be the case for "custom" delegates.

Three new issues:

https://peach.sonarsource.com/project/issues?issues=AYtq0ttJgPIhmeQQhQ_D&open=AYtq0ttJgPIhmeQQhQ_D&id=dotnet-runtime

https://peach.sonarsource.com/project/issues?issues=AYtpf6OqhLL7WIb3wRCY&open=AYtpf6OqhLL7WIb3wRCY&id=wcf

Both examples are identical (copy/paste).

TP: GetIdentifier() for the argument expression supports more than just simple IdentifierNameSyntax and more arguments are considered to be named (see also #8253 on how to extend support even more here)

https://peach.sonarsource.com/project/issues?issues=AYtpjmQYUJUfslckQTtU&open=AYtpjmQYUJUfslckQTtU&id=omnisharp-server

TP: this() object initializer call support

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.

Fix S2234 FN: primary constructors for records, classes and structs Fix S2234 FN: this and base constructors
2 participants