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

Add support for generating the .. x ? [y] : [] pattern in a collection-expression #69280

Merged
merged 12 commits into from
Aug 2, 2023
Next Next commit
Simplify
  • Loading branch information
CyrusNajmabadi committed Jul 28, 2023
commit 00eadd688f39ed22ceeec47f3bc8e22166a4c732
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal abstract partial class AbstractUseCollectionInitializerDiagnosticAnalyz
TInvocationExpressionSyntax,
TExpressionStatementSyntax,
TForeachStatementSyntax,
TIfStatementSyntax,
TVariableDeclaratorSyntax>
: AbstractBuiltInCodeStyleDiagnosticAnalyzer
where TSyntaxKind : struct
Expand All @@ -40,6 +41,7 @@ internal abstract partial class AbstractUseCollectionInitializerDiagnosticAnalyz
where TInvocationExpressionSyntax : TExpressionSyntax
where TExpressionStatementSyntax : TStatementSyntax
where TForeachStatementSyntax : TStatementSyntax
where TIfStatementSyntax : TStatementSyntax
where TVariableDeclaratorSyntax : SyntaxNode
{

Expand Down Expand Up @@ -127,11 +129,14 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien
if (containingStatement == null)
return;

var (matches, shouldUseCollectionExpression) = GetMatches();
// If we got no matches, then we def can't convert this.
if (matches.IsDefaultOrEmpty)
// Analyze the surrounding statements. First, try a broader set of statements if the language supports
// collection expressions.
var result = GetCollectionExpressionMatches() ?? GetCollectionInitializerMatches();
if (result is null)
return;

var (matches, shouldUseCollectionExpression) = result.Value;

var nodes = ImmutableArray.Create<SyntaxNode>(containingStatement).AddRange(matches.Select(static m => m.Statement));
var syntaxFacts = GetSyntaxFacts();
if (syntaxFacts.ContainsInterleavedDirective(nodes, cancellationToken))
Expand All @@ -150,29 +155,47 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien

return;

(ImmutableArray<Match<TStatementSyntax>> matches, bool shouldUseCollectionExpression) GetMatches()
(ImmutableArray<Match<TStatementSyntax>> matches, bool shouldUseCollectionExpression)? GetCollectionInitializerMatches()
{
// Analyze the surrounding statements. First, try a broader set of statements if the language supports
// collection expressions.
var analyzeForCollectionExpression = AreCollectionExpressionsSupported();
var matches = UseCollectionInitializerAnalyzer<
TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TForeachStatementSyntax, TVariableDeclaratorSyntax>.Analyze(
semanticModel, GetSyntaxFacts(), objectCreationExpression, analyzeForCollectionExpression, cancellationToken);
semanticModel, GetSyntaxFacts(), objectCreationExpression, areCollectionExpressionsSupported: false, cancellationToken);

// If analysis failed, we can't change this, no matter what.
if (matches.IsDefault)
return null;

// if this was a normal (non-collection-expr) analysis, then just return what we got.
if (!analyzeForCollectionExpression)
return (matches, shouldUseCollectionExpression: false);
// for collection initializers we always want at least one element to add to the initializer. In other
// words, we don't want to suggest changing `new List<int>()` to `new List<int>() { }` as that's just
// noise.
if (matches.IsEmpty)
return null;

// If we succeeded in finding matches, and this is a location a collection expression is legal in, then convert to that.
if (!matches.IsDefaultOrEmpty && CanUseCollectionExpression(semanticModel, objectCreationExpression, cancellationToken))
return (matches, analyzeForCollectionExpression);
return (matches, shouldUseCollectionExpression: false);
}

(ImmutableArray<Match<TStatementSyntax>> matches, bool shouldUseCollectionExpression)? GetCollectionExpressionMatches()
{
// Don't bother analyzing for the collection expression case if the lang/version doesn't even support it.
if (!AreCollectionExpressionsSupported())
return null;

// we tried collection expression, and were not successful. try again, this time without collection exprs.
analyzeForCollectionExpression = false;
matches = UseCollectionInitializerAnalyzer<
var matches = UseCollectionInitializerAnalyzer<
TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TForeachStatementSyntax, TVariableDeclaratorSyntax>.Analyze(
semanticModel, GetSyntaxFacts(), objectCreationExpression, analyzeForCollectionExpression, cancellationToken);
return (matches, analyzeForCollectionExpression);
semanticModel, GetSyntaxFacts(), objectCreationExpression, areCollectionExpressionsSupported: true, cancellationToken);

// If analysis failed, we can't change this, no matter what.
if (matches.IsDefault)
return null;

// Analysis succeeded. Note: for collection expressions, it's fine for this result to be empty. In
// other words, it's ok to offer changing `new List<int>() { 1 }` (on its own) to `[1]`.

// Check if it would actually be legal to use a collection expression here though.
if (!CanUseCollectionExpression(semanticModel, objectCreationExpression, cancellationToken))
return null;

return (matches, shouldUseCollectionExpression: true);
}

bool AreCollectionExpressionsSupported()
Expand Down