Skip to content

Commit f292bbd

Browse files
committed
Merge remote-tracking branch 'refs/remotes/DotNetAnalyzers/master'
2 parents d7b033d + 564fcc5 commit f292bbd

24 files changed

Lines changed: 543 additions & 66 deletions

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/IndentationHelper.cs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Helpers
55
{
66
using Microsoft.CodeAnalysis;
77
using Microsoft.CodeAnalysis.CSharp;
8+
using StyleCop.Analyzers.Helpers.ObjectPools;
89

910
/// <summary>
1011
/// Provides helper methods to work with indentation.
@@ -43,7 +44,7 @@ public static SyntaxToken GetFirstTokenOnTextLine(SyntaxToken token)
4344
/// <returns>The number of steps that the node is indented.</returns>
4445
public static int GetIndentationSteps(IndentationOptions indentationOptions, SyntaxNode node)
4546
{
46-
return GetIndentationSteps(indentationOptions, node.GetLeadingTrivia());
47+
return GetIndentationSteps(indentationOptions, node.SyntaxTree, node.GetLeadingTrivia());
4748
}
4849

4950
/// <summary>
@@ -54,7 +55,7 @@ public static int GetIndentationSteps(IndentationOptions indentationOptions, Syn
5455
/// <returns>The number of steps that the token is indented.</returns>
5556
public static int GetIndentationSteps(IndentationOptions indentationOptions, SyntaxToken token)
5657
{
57-
return GetIndentationSteps(indentationOptions, token.LeadingTrivia);
58+
return GetIndentationSteps(indentationOptions, token.SyntaxTree, token.LeadingTrivia);
5859
}
5960

6061
/// <summary>
@@ -92,25 +93,38 @@ public static SyntaxTrivia GenerateWhitespaceTrivia(IndentationOptions indentati
9293
return SyntaxFactory.Whitespace(GenerateIndentationString(indentationOptions, indentationSteps));
9394
}
9495

95-
private static int GetIndentationSteps(IndentationOptions indentationOptions, SyntaxTriviaList leadingTrivia)
96+
private static int GetIndentationSteps(IndentationOptions indentationOptions, SyntaxTree syntaxTree, SyntaxTriviaList leadingTrivia)
9697
{
97-
SyntaxTriviaList.Reversed reversed = leadingTrivia.Reverse();
98-
int indentationCount = 0;
98+
var triviaSpan = syntaxTree.GetLineSpan(leadingTrivia.FullSpan);
9999

100-
foreach (SyntaxTrivia trivia in reversed)
100+
// There is no indentation when the leading trivia doesn't begin at the start of the line.
101+
if ((triviaSpan.StartLinePosition == triviaSpan.EndLinePosition) && (triviaSpan.StartLinePosition.Character > 0))
102+
{
103+
return 0;
104+
}
105+
106+
var builder = StringBuilderPool.Allocate();
107+
108+
foreach (SyntaxTrivia trivia in leadingTrivia.Reverse())
101109
{
102110
if (!trivia.IsKind(SyntaxKind.WhitespaceTrivia))
103111
{
104112
break;
105113
}
106114

107-
foreach (char c in trivia.ToFullString())
108-
{
109-
indentationCount += c == '\t' ? indentationOptions.TabSize : 1;
110-
}
115+
builder.Insert(0, trivia.ToFullString());
111116
}
112117

113-
return indentationCount / indentationOptions.IndentationSize;
118+
var tabSize = indentationOptions.TabSize;
119+
var indentationCount = 0;
120+
for (var i = 0; i < builder.Length; i++)
121+
{
122+
indentationCount += builder[i] == '\t' ? tabSize - (indentationCount % tabSize) : 1;
123+
}
124+
125+
StringBuilderPool.ReturnAndFree(builder);
126+
127+
return (indentationCount + (indentationOptions.IndentationSize / 2)) / indentationOptions.IndentationSize;
114128
}
115129
}
116130
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1600UnitTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ public async Task TestRegressionMethodGlobalNamespaceAsync(string code, int colu
3333

3434
var expected = new[]
3535
{
36-
this.CSharpDiagnostic().WithLocation(4, column),
3736
new DiagnosticResult
3837
{
3938
Id = "CS0116",
4039
Severity = DiagnosticSeverity.Error,
4140
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, column) },
4241
Message = "A namespace cannot directly contain members such as fields or methods"
43-
}
42+
},
43+
this.CSharpDiagnostic().WithLocation(4, column)
4444
};
4545

4646
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1648UnitTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ public async Task TestTypeWithEmptyBaseListAsync(string declaration)
8181
[InlineData("string this [string f] { get { return f; } }")]
8282
[InlineData("event System.Action foo;")]
8383
[InlineData("event System.Action Foo { add { } remove { } }")]
84+
[InlineData("~Test() { }")]
85+
[InlineData("public static Test operator +(Test value) { return value; }")]
86+
[InlineData("public static explicit operator Test(int value) { return new Test(); }")]
8487
public async Task TestMemberThatShouldNotHaveInheritDocAsync(string declaration)
8588
{
8689
var testCode = @"class Test

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ public async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeK
169169

170170
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType`3.cs", 3, 13 + typeKeyword.Length);
171171
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType`3.cs").ConfigureAwait(false);
172+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
172173
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType{T1,T2,T3}.cs").ConfigureAwait(false);
173174
await this.VerifyRenameAsync(testCode, "TestType{T1,T2,T3}.cs", CancellationToken.None).ConfigureAwait(false);
174175
}
@@ -194,6 +195,9 @@ public async Task VerifyMetadataNamingConventionForGenericTypeAsync(string typeK
194195

195196
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType{T1,T2,T3}.cs", 3, 13 + typeKeyword.Length);
196197
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType{T1,T2,T3}.cs").ConfigureAwait(false);
198+
199+
expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType.cs", 3, 13 + typeKeyword.Length);
200+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
197201
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType`3.cs").ConfigureAwait(false);
198202
await this.VerifyRenameAsync(testCode, "TestType`3.cs", CancellationToken.None).ConfigureAwait(false);
199203
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.HelperTests
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis.Formatting;
11+
using Microsoft.CodeAnalysis.Text;
12+
using StyleCop.Analyzers.Helpers;
13+
using StyleCop.Analyzers.Test.Helpers;
14+
using Xunit;
15+
16+
/// <summary>
17+
/// Unit tests for the <see cref="IndentationHelper"/> class.
18+
/// </summary>
19+
public class IndentationHelperTests
20+
{
21+
public static readonly IEnumerable<object[]> IndentationTestData = new[]
22+
{
23+
// no indentation
24+
new object[] { string.Empty, 0, 4, 4 },
25+
26+
// 1 space
27+
new object[] { " ", 0, 4, 4 },
28+
29+
// 2 spaces
30+
new object[] { " ", 1, 4, 4 },
31+
32+
// 3 spaces
33+
new object[] { " ", 1, 4, 4 },
34+
35+
// 3 spaces, indentation size = 2
36+
new object[] { " ", 1, 4, 4 },
37+
38+
// 4 spaces
39+
new object[] { " ", 1, 4, 4 },
40+
41+
// 5 spaces
42+
new object[] { " ", 1, 4, 4 },
43+
44+
// 6 spaces
45+
new object[] { " ", 2, 4, 4 },
46+
47+
// 7 spaces
48+
new object[] { " ", 2, 4, 4 },
49+
50+
// 8 spaces
51+
new object[] { " ", 2, 4, 4 },
52+
53+
// 8 spaces indentation, indentation size = 2
54+
new object[] { " ", 4, 2, 4 },
55+
56+
// 9 spaces indentation, indentation size = 3
57+
new object[] { " ", 3, 3, 4 },
58+
59+
// single tab
60+
new object[] { "\t", 1, 4, 4 },
61+
62+
// multiple tabs
63+
new object[] { "\t\t\t", 3, 4, 4 },
64+
65+
// multiple tabs (difference between indentation size and tab size)
66+
new object[] { "\t\t\t", 6, 3, 6 },
67+
68+
// spaces + tabs mix (regression for #1036)
69+
new object[] { " \t \t \t \t", 4, 4, 4 },
70+
71+
// 1 space followed by a tab
72+
new object[] { " \t", 1, 4, 4 },
73+
74+
// 2 spaces followed by a tab
75+
new object[] { " \t", 1, 4, 4 },
76+
77+
// 3 spaces followed by a tab
78+
new object[] { " \t", 1, 4, 4 },
79+
80+
// 4 spaces followed by a tab
81+
new object[] { " \t", 2, 4, 4 },
82+
83+
// tab followed by 1 space
84+
new object[] { "\t ", 1, 4, 4 },
85+
86+
// tab followed by 2 space
87+
new object[] { "\t ", 2, 4, 4 },
88+
89+
// tab followed by 3 spaces
90+
new object[] { "\t ", 2, 4, 4 },
91+
92+
// tab followed by 4 spaces
93+
new object[] { "\t ", 2, 4, 4 }
94+
};
95+
96+
private const string TestProjectName = "TestProject";
97+
private const string TestFilename = "Test0.cs";
98+
99+
/// <summary>
100+
/// Verify the workings of IndentationHelper.GetIndentationSteps./>
101+
/// </summary>
102+
/// <param name="indentationString">The indentation string to use with the test.</param>
103+
/// <param name="expectedIndentationSteps">The expected number of indentation steps.</param>
104+
/// <param name="indentationSize">The indentation size to use.</param>
105+
/// <param name="tabSize">The tab size to use.</param>
106+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
107+
[Theory]
108+
[MemberData(nameof(IndentationTestData))]
109+
public async Task VerifyGetIndentationStepsAsync(string indentationString, int expectedIndentationSteps, int indentationSize, int tabSize)
110+
{
111+
var testSource = $"{indentationString}public class TestClass {{}}";
112+
var document = CreateTestDocument(testSource, indentationSize, false, tabSize);
113+
var indentationOptions = IndentationOptions.FromDocument(document);
114+
115+
var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
116+
var firstToken = syntaxRoot.GetFirstToken();
117+
118+
Assert.Equal(expectedIndentationSteps, IndentationHelper.GetIndentationSteps(indentationOptions, firstToken));
119+
}
120+
121+
/// <summary>
122+
/// Verify the that IndentationHelper.GetIndentationSteps will return zero (0) for tokens that are not the first token on a line.
123+
/// </summary>
124+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
125+
[Fact]
126+
public async Task VerifyGetIndentationStepsForTokenNotAtStartOfLineAsync()
127+
{
128+
var testSource = " public class TestClass {}";
129+
var document = CreateTestDocument(testSource);
130+
var indentationOptions = IndentationOptions.FromDocument(document);
131+
132+
var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
133+
var secondToken = syntaxRoot.GetFirstToken().GetNextToken();
134+
135+
Assert.Equal(0, IndentationHelper.GetIndentationSteps(indentationOptions, secondToken));
136+
}
137+
138+
private static Document CreateTestDocument(string source, int indentationSize = 4, bool useTabs = false, int tabSize = 4)
139+
{
140+
var workspace = new AdhocWorkspace();
141+
workspace.Options = workspace.Options
142+
.WithChangedOption(FormattingOptions.IndentationSize, LanguageNames.CSharp, indentationSize)
143+
.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, useTabs)
144+
.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, tabSize);
145+
146+
var projectId = ProjectId.CreateNewId();
147+
var documentId = DocumentId.CreateNewId(projectId);
148+
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);
149+
150+
var solution = workspace.CurrentSolution
151+
.AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp)
152+
.WithProjectCompilationOptions(projectId, compilationOptions)
153+
.AddMetadataReference(projectId, MetadataReferences.CorlibReference)
154+
.AddMetadataReference(projectId, MetadataReferences.SystemReference)
155+
.AddMetadataReference(projectId, MetadataReferences.SystemCoreReference)
156+
.AddMetadataReference(projectId, MetadataReferences.CSharpSymbolsReference)
157+
.AddMetadataReference(projectId, MetadataReferences.CodeAnalysisReference)
158+
.AddDocument(documentId, TestFilename, SourceText.From(source));
159+
160+
return solution.GetDocument(documentId);
161+
}
162+
}
163+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace TestHelper
1313
using Microsoft.CodeAnalysis.CSharp;
1414
using Microsoft.CodeAnalysis.Diagnostics;
1515
using Microsoft.CodeAnalysis.Text;
16+
using StyleCop.Analyzers.Test.Helpers;
1617

1718
/// <summary>
1819
/// Class for turning strings into documents and getting the diagnostics on them.
@@ -22,12 +23,6 @@ public abstract partial class DiagnosticVerifier
2223
{
2324
private const string SettingsFileName = "stylecop.json";
2425

25-
private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location).WithAliases(ImmutableArray.Create("global", "corlib"));
26-
private static readonly MetadataReference SystemReference = MetadataReference.CreateFromFile(typeof(System.Diagnostics.Debug).Assembly.Location).WithAliases(ImmutableArray.Create("global", "system"));
27-
private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
28-
private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location);
29-
private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location);
30-
3126
private static readonly string DefaultFilePathPrefix = "Test";
3227
private static readonly string CSharpDefaultFileExt = "cs";
3328
private static readonly string VisualBasicDefaultExt = "vb";
@@ -135,11 +130,11 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language)
135130
.CurrentSolution
136131
.AddProject(projectId, TestProjectName, TestProjectName, language)
137132
.WithProjectCompilationOptions(projectId, compilationOptions)
138-
.AddMetadataReference(projectId, CorlibReference)
139-
.AddMetadataReference(projectId, SystemReference)
140-
.AddMetadataReference(projectId, SystemCoreReference)
141-
.AddMetadataReference(projectId, CSharpSymbolsReference)
142-
.AddMetadataReference(projectId, CodeAnalysisReference);
133+
.AddMetadataReference(projectId, MetadataReferences.CorlibReference)
134+
.AddMetadataReference(projectId, MetadataReferences.SystemReference)
135+
.AddMetadataReference(projectId, MetadataReferences.SystemCoreReference)
136+
.AddMetadataReference(projectId, MetadataReferences.CSharpSymbolsReference)
137+
.AddMetadataReference(projectId, MetadataReferences.CodeAnalysisReference);
143138

144139
var settings = this.GetSettings();
145140
if (!string.IsNullOrEmpty(settings))
@@ -224,10 +219,10 @@ protected virtual Project CreateProject(string[] sources, string language = Lang
224219
/// </summary>
225220
/// <param name="diagnostics">A collection of <see cref="Diagnostic"/>s to be sorted.</param>
226221
/// <returns>A collection containing the input <paramref name="diagnostics"/>, sorted by
227-
/// <see cref="Diagnostic.Location"/>.</returns>
222+
/// <see cref="Diagnostic.Location"/> and <see cref="Diagnostic.Id"/>.</returns>
228223
private static Diagnostic[] SortDistinctDiagnostics(IEnumerable<Diagnostic> diagnostics)
229224
{
230-
return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
225+
return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ThenBy(d => d.Id).ToArray();
231226
}
232227

233228
/// <summary>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.Helpers
5+
{
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
11+
/// <summary>
12+
/// Metadata references used to create test projects.
13+
/// </summary>
14+
internal static class MetadataReferences
15+
{
16+
internal static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location).WithAliases(ImmutableArray.Create("global", "corlib"));
17+
internal static readonly MetadataReference SystemReference = MetadataReference.CreateFromFile(typeof(System.Diagnostics.Debug).Assembly.Location).WithAliases(ImmutableArray.Create("global", "system"));
18+
internal static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
19+
internal static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location);
20+
internal static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location);
21+
}
22+
}

0 commit comments

Comments
 (0)