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

Port roslyn-analyer style rules to experimental rules in roslyn. #50358

Merged
merged 40 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8aacc22
Port 'wrap embedded statement' to Roslyn
CyrusNajmabadi Jan 9, 2021
ff4a8ff
Add options
CyrusNajmabadi Jan 9, 2021
3e5867a
Switch
CyrusNajmabadi Jan 9, 2021
fe54d8e
Move
CyrusNajmabadi Jan 9, 2021
d59d427
Fix
CyrusNajmabadi Jan 9, 2021
4d916ae
Port 'consecutive brace placement' to Roslyn
CyrusNajmabadi Jan 9, 2021
5af2e88
Add UI
CyrusNajmabadi Jan 9, 2021
2a41e0b
Port C# side of 'multiple blank lines' to Roslyn
CyrusNajmabadi Jan 9, 2021
baf628a
Add VB tests
CyrusNajmabadi Jan 9, 2021
4f855da
Refine
CyrusNajmabadi Jan 9, 2021
6152063
Fix
CyrusNajmabadi Jan 9, 2021
35431ed
Fix
CyrusNajmabadi Jan 9, 2021
64f10e3
Merge impls
CyrusNajmabadi Jan 9, 2021
027622e
Revert
CyrusNajmabadi Jan 9, 2021
afddafb
Use EnforceOnBuildValues
CyrusNajmabadi Jan 10, 2021
896066d
Port consecutive statement analyzer
CyrusNajmabadi Jan 10, 2021
6f698eb
Add VBside
CyrusNajmabadi Jan 10, 2021
10f5ad2
Add UI
CyrusNajmabadi Jan 10, 2021
ff8fcd5
VBtoo
CyrusNajmabadi Jan 10, 2021
c6cb66a
Add test
CyrusNajmabadi Jan 10, 2021
7d46c47
Invert values
CyrusNajmabadi Jan 11, 2021
5cfcadd
Fixup tests
CyrusNajmabadi Jan 11, 2021
6f10224
Fixup tests
CyrusNajmabadi Jan 11, 2021
186145b
Simplify
CyrusNajmabadi Jan 12, 2021
5b50c71
Add loop statement
CyrusNajmabadi Jan 12, 2021
1369ba6
Merge remote-tracking branch 'upstream/master' into experimentalForma…
CyrusNajmabadi Jan 14, 2021
1003442
Add fixer to put : on the same line as his/base
CyrusNajmabadi Jan 14, 2021
00e3545
Add UI
CyrusNajmabadi Jan 14, 2021
2999f4f
Merge remote-tracking branch 'upstream/master' into experimentalForma…
CyrusNajmabadi Jan 18, 2021
11c0c4e
Fixup tests
CyrusNajmabadi Jan 18, 2021
697aacb
Merge branch 'master' into experimentalFormatting
CyrusNajmabadi Jan 28, 2021
c154f74
Formatting
CyrusNajmabadi Jan 31, 2021
e7970d4
Merge remote-tracking branch 'upstream/master' into experimentalForma…
CyrusNajmabadi Feb 1, 2021
e3f3192
Use syntax facts
CyrusNajmabadi Feb 1, 2021
77646aa
Merge remote-tracking branch 'upstream/master' into experimentalForma…
CyrusNajmabadi Feb 9, 2021
2a81547
Update IDEDiagnosticIDConfigurationTests.cs
CyrusNajmabadi Feb 10, 2021
b70814c
Merge remote-tracking branch 'upstream/master' into experimentalForma…
CyrusNajmabadi Feb 10, 2021
00e925d
Fix tests
CyrusNajmabadi Feb 10, 2021
a31df02
Merge branch 'experimentalFormatting' of https://github.com/CyrusNajm…
CyrusNajmabadi Feb 10, 2021
7ab8b99
Fix tests
CyrusNajmabadi Feb 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<Compile Include="$(MSBuildThisFileDirectory)AddBraces\CSharpAddBracesDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)AddRequiredParentheses\CSharpAddRequiredPatternParenthesesDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConvertTypeofToNameof\CSharpConvertTypeOfToNameOfDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NewLines\ConsecutiveBracePlacement\CSharpConsecutiveBracePlacementDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NewLines\MultipleBlankLines\CSharpMultipleBlankLinesDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RemoveRedundantEquality\CSharpRemoveRedundantEqualityDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessaryDiscardDesignation\CSharpRemoveUnnecessaryDiscardDesignationDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessarySuppressions\CSharpRemoveUnnecessaryPragmaSuppressionsDiagnosticAnalyzer.cs" />
Expand Down Expand Up @@ -103,6 +105,7 @@
<Compile Include="$(MSBuildThisFileDirectory)UseSimpleUsingStatement\UseSimpleUsingStatementDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseThrowExpression\CSharpUseThrowExpressionDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ValidateFormatString\CSharpValidateFormatStringDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NewLines\WrapEmbeddedStatement\CSharpWrapEmbeddedStatementDiagnosticAnalyzer.cs" />
</ItemGroup>
<ItemGroup Condition="'$(DefaultLanguageSourceExtension)' != '' AND '$(BuildingInsideVisualStudio)' != 'true'">
<ExpectedCompile Include="$(MSBuildThisFileDirectory)**\*$(DefaultLanguageSourceExtension)" />
Expand Down
6 changes: 6 additions & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,10 @@
<data name="Remove_unnessary_discard" xml:space="preserve">
<value>Remove unnecessary discard</value>
</data>
<data name="Embedded_statements_must_be_on_their_own_line" xml:space="preserve">
<value>Embedded statements must be on their own line</value>
</data>
<data name="Consecutive_braces_must_not_have_a_blank_between_them" xml:space="preserve">
<value>Consecutive braces must not have blank line between them</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.NewLines.ConsecutiveBracePlacement
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpConsecutiveBracePlacementDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
public CSharpConsecutiveBracePlacementDiagnosticAnalyzer()
: base(IDEDiagnosticIds.ConsecutiveBracePlacementDiagnosticId,
EnforceOnBuild.WhenExplicitlyEnabled,
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
CSharpCodeStyleOptions.DisallowBlankLinesBetweenConsecutiveBraces,
LanguageNames.CSharp,
new LocalizableResourceString(
nameof(CSharpAnalyzersResources.Consecutive_braces_must_not_have_a_blank_between_them), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
}

public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;

protected override void InitializeWorker(AnalysisContext context)
=> context.RegisterSyntaxTreeAction(AnalyzeTree);

private void AnalyzeTree(SyntaxTreeAnalysisContext context)
{
var option = context.GetOption(CSharpCodeStyleOptions.DisallowBlankLinesBetweenConsecutiveBraces);
if (!option.Value)
return;

using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var stack);
Recurse(context, option.Notification.Severity, stack);
}

private void Recurse(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, ArrayBuilder<SyntaxNode> stack)
{
var tree = context.Tree;
var cancellationToken = context.CancellationToken;

var root = tree.GetRoot(cancellationToken);
var text = tree.GetText(cancellationToken);

stack.Add(root);
while (stack.Count > 0)
{
cancellationToken.ThrowIfCancellationRequested();

var current = stack.Last();
stack.RemoveLast();

// Don't bother analyzing nodes that have syntax errors in them.
if (current.ContainsDiagnostics && current.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
continue;

foreach (var child in current.ChildNodesAndTokens())
{
if (child.IsNode)
stack.Add(child.AsNode()!);
else if (child.IsToken)
ProcessToken(context, severity, text, child.AsToken());
}
}
}

private void ProcessToken(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, SourceText text, SyntaxToken token)
{
if (!HasExcessBlankLinesAfter(text, token, out var secondBrace, out _))
return;

context.ReportDiagnostic(DiagnosticHelper.Create(
this.Descriptor,
secondBrace.GetLocation(),
severity,
additionalLocations: null,
properties: null));
}

public static bool HasExcessBlankLinesAfter(
SourceText text, SyntaxToken token,
out SyntaxToken secondBrace,
out SyntaxTrivia endOfLineTrivia)
{
secondBrace = default;
endOfLineTrivia = default;
if (!token.IsKind(SyntaxKind.CloseBraceToken))
return false;

var nextToken = token.GetNextToken();
if (!nextToken.IsKind(SyntaxKind.CloseBraceToken))
return false;

var firstBrace = token;
secondBrace = nextToken;

// two } tokens. They need to be on the same line, or if they are not on subsequent lines, then there needs
// to be more than whitespace between them.
var lines = text.Lines;
var firstBraceLine = lines.GetLineFromPosition(firstBrace.SpanStart).LineNumber;
var secondBraceLine = lines.GetLineFromPosition(secondBrace.SpanStart).LineNumber;

var lineCount = secondBraceLine - firstBraceLine;

// if they're both on the same line, or one line apart, then there's no problem.
if (lineCount <= 1)
return false;

// they're multiple lines apart. This i not ok if those lines are all whitespace.
for (var currentLine = firstBraceLine + 1; currentLine < secondBraceLine; currentLine++)
{
if (!IsAllWhitespace(lines[currentLine]))
return false;
}

endOfLineTrivia = secondBrace.LeadingTrivia.Last(t => t.IsKind(SyntaxKind.EndOfLineTrivia));
return endOfLineTrivia != default;
}

private static bool IsAllWhitespace(TextLine textLine)
{
var text = textLine.Text!;
for (var i = textLine.Start; i < textLine.End; i++)
{
if (!SyntaxFacts.IsWhitespace(text[i]))
return false;
}

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.NewLines.MultipleBlankLines;

namespace Microsoft.CodeAnalysis.CSharp.NewLines.MultipleBlankLines
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpMultipleBlankLinesDiagnosticAnalyzer : AbstractMultipleBlankLinesDiagnosticAnalyzer
{
protected override bool IsEndOfLine(SyntaxTrivia trivia)
=> trivia.IsKind(SyntaxKind.EndOfLineTrivia);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeAnalysis.CSharp.NewLines.WrapEmbeddedStatement
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpWrapEmbeddedStatementDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
public CSharpWrapEmbeddedStatementDiagnosticAnalyzer()
: base(IDEDiagnosticIds.WrapEmbeddedStatementDiagnosticId,
EnforceOnBuild.WhenExplicitlyEnabled,
CSharpCodeStyleOptions.DisallowEmbeddedStatementsOnSameLine,
LanguageNames.CSharp,
new LocalizableResourceString(
nameof(CSharpAnalyzersResources.Embedded_statements_must_be_on_their_own_line), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
}

public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;

protected override void InitializeWorker(AnalysisContext context)
=> context.RegisterSyntaxTreeAction(AnalyzeTree);

private void AnalyzeTree(SyntaxTreeAnalysisContext context)
{
var option = context.GetOption(CSharpCodeStyleOptions.DisallowEmbeddedStatementsOnSameLine);
if (!option.Value)
return;

Recurse(context, option.Notification.Severity, context.Tree.GetRoot(context.CancellationToken));
}

private void Recurse(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, SyntaxNode node)
{
context.CancellationToken.ThrowIfCancellationRequested();

// Don't bother analyzing nodes that have syntax errors in them.
if (node.ContainsDiagnostics)
return;

// Report on the topmost statement that has an issue. No need to recurse further at that point. Note: the
// fixer will fix up all statements, but we don't want to clutter things with lots of diagnostics on the
// same line.
if (node is StatementSyntax statement &&
CheckStatementSyntax(context, severity, statement))
{
return;
}

foreach (var child in node.ChildNodesAndTokens())
{
if (child.IsNode)
Recurse(context, severity, child.AsNode()!);
}
}

private bool CheckStatementSyntax(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, StatementSyntax statement)
{
if (!StatementNeedsWrapping(statement))
return false;

var additionalLocations = ImmutableArray.Create(statement.GetLocation());
context.ReportDiagnostic(DiagnosticHelper.Create(
this.Descriptor,
statement.GetFirstToken().GetLocation(),
severity,
additionalLocations,
properties: null));
return true;
}

public static bool StatementNeedsWrapping(StatementSyntax statement)
{
// Statement has to be parented by another statement (or an else-clause) to count.
var parent = statement.Parent;
var parentIsElseClause = parent.IsKind(SyntaxKind.ElseClause);

if (!(parent is StatementSyntax || parentIsElseClause))
return false;

// `else if` is always allowed.
if (statement.IsKind(SyntaxKind.IfStatement) && parentIsElseClause)
return false;

var statementStartToken = statement.GetFirstToken();

// we have to have a newline between the start of this statement and the previous statement.
if (ContainsEndOfLineBetween(statementStartToken.GetPreviousToken(), statementStartToken))
return false;

// Looks like a statement that might need wrapping. However, we do suppress wrapping for a few well known
// acceptable cases.

if (parent.IsKind(SyntaxKind.Block))
{
// Blocks can be on a single line if parented by a member/accessor/lambda.
// And if they only contain a single statement at most within them.
var blockParent = parent.Parent;
if (blockParent is MemberDeclarationSyntax or
AccessorDeclarationSyntax or
AnonymousFunctionExpressionSyntax)
{
if (parent.DescendantNodes().OfType<StatementSyntax>().Count() <= 1)
return false;
}
}

return true;
}

public static bool ContainsEndOfLineBetween(SyntaxToken previous, SyntaxToken next)
=> ContainsEndOfLine(previous.TrailingTrivia) || ContainsEndOfLine(next.LeadingTrivia);

private static bool ContainsEndOfLine(SyntaxTriviaList triviaList)
{
foreach (var trivia in triviaList)
{
if (trivia.IsKind(SyntaxKind.EndOfLineTrivia))
return true;
}

return false;
}
}
}
10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading