Skip to content

Commit e313f8c

Browse files
committed
Merge remote-tracking branch 'DotNetAnalyzers/master'
2 parents d92b483 + 76adb05 commit e313f8c

8 files changed

Lines changed: 173 additions & 43 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace StyleCop.Analyzers.DocumentationRules
1919
using Microsoft.CodeAnalysis.Formatting;
2020
using StyleCop.Analyzers.Helpers;
2121
using StyleCop.Analyzers.Settings.ObjectModel;
22+
using Path = System.IO.Path;
2223

2324
/// <summary>
2425
/// Implements a code fix for file header diagnostics.
@@ -137,8 +138,9 @@ private static SyntaxNode ReplaceWellFormedMultiLineCommentHeader(Document docum
137138
// Pad line that used to be next to a /*
138139
triviaStringParts[0] = commentIndentation + interlinePadding + " " + triviaStringParts[0];
139140
StringBuilder sb = StringBuilderPool.Allocate();
141+
string fileName = Path.GetFileName(document.FilePath);
140142
var copyrightText = commentIndentation + interlinePadding + " " +
141-
GetCopyrightText(commentIndentation + interlinePadding, settings.DocumentationRules.CopyrightText, newLineText);
143+
GetCopyrightText(commentIndentation + interlinePadding, settings.DocumentationRules.GetCopyrightText(fileName), newLineText);
142144
var newHeader = WrapInXmlComment(commentIndentation + interlinePadding, copyrightText, document.Name, settings, newLineText);
143145

144146
sb.Append(commentIndentation);
@@ -354,18 +356,18 @@ private static SyntaxNode AddHeader(Document document, SyntaxNode root, string n
354356
return root.WithLeadingTrivia(newTrivia);
355357
}
356358

357-
private static SyntaxTriviaList CreateNewHeader(string prefixWithLeadingSpaces, string filename, StyleCopSettings settings, string newLineText)
359+
private static SyntaxTriviaList CreateNewHeader(string prefixWithLeadingSpaces, string fileName, StyleCopSettings settings, string newLineText)
358360
{
359-
var copyrightText = prefixWithLeadingSpaces + " " + GetCopyrightText(prefixWithLeadingSpaces, settings.DocumentationRules.CopyrightText, newLineText);
361+
var copyrightText = prefixWithLeadingSpaces + " " + GetCopyrightText(prefixWithLeadingSpaces, settings.DocumentationRules.GetCopyrightText(fileName), newLineText);
360362
var newHeader = settings.DocumentationRules.XmlHeader
361-
? WrapInXmlComment(prefixWithLeadingSpaces, copyrightText, filename, settings, newLineText)
363+
? WrapInXmlComment(prefixWithLeadingSpaces, copyrightText, fileName, settings, newLineText)
362364
: copyrightText;
363365
return SyntaxFactory.ParseLeadingTrivia(newHeader);
364366
}
365367

366-
private static string WrapInXmlComment(string prefixWithLeadingSpaces, string copyrightText, string filename, StyleCopSettings settings, string newLineText)
368+
private static string WrapInXmlComment(string prefixWithLeadingSpaces, string copyrightText, string fileName, StyleCopSettings settings, string newLineText)
367369
{
368-
string encodedFilename = new XAttribute("t", filename).ToString().Substring(2).Trim('"');
370+
string encodedFilename = new XAttribute("t", fileName).ToString().Substring(2).Trim('"');
369371
string encodedCompanyName = new XAttribute("t", settings.DocumentationRules.CompanyName).ToString().Substring(2).Trim('"');
370372
string encodedCopyrightText = new XText(copyrightText).ToString();
371373

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ public class NoXmlFileHeaderUnitTests : CodeFixVerifier
2525
""copyrightText"": ""Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information."",
2626
""variables"": {
2727
""licenseName"": ""???"",
28-
""licenseFile"": ""LICENSE"",
28+
""licenseFile"": ""LICENSE""
2929
},
3030
""xmlHeader"": false
3131
}
3232
}
3333
}
3434
";
3535

36+
private string currentTestSettings = TestSettings;
37+
3638
/// <summary>
3739
/// Verifies that the analyzer will report <see cref="FileHeaderAnalyzers.SA1633DescriptorMissing"/> for
3840
/// projects not using XML headers when the file is completely missing a header.
@@ -48,6 +50,89 @@ public virtual async Task TestNoFileHeaderAsync()
4850
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
4951
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
5052
53+
namespace Foo
54+
{
55+
}
56+
";
57+
58+
var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1);
59+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
60+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
61+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
62+
}
63+
64+
/// <summary>
65+
/// Verifies that the built-in variable <c>fileName</c> works as expected.
66+
/// </summary>
67+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
68+
[Fact]
69+
public virtual async Task TestFileNameBuiltInVariableAsync()
70+
{
71+
this.currentTestSettings = @"
72+
{
73+
""settings"": {
74+
""documentationRules"": {
75+
""companyName"": ""FooCorp"",
76+
""copyrightText"": ""{fileName} Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information."",
77+
""variables"": {
78+
""licenseName"": ""???"",
79+
""licenseFile"": ""LICENSE""
80+
},
81+
""xmlHeader"": false
82+
}
83+
}
84+
}
85+
";
86+
87+
var testCode = @"namespace Foo
88+
{
89+
}
90+
";
91+
var fixedCode = @"// Test0.cs Copyright (c) FooCorp. All rights reserved.
92+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
93+
94+
namespace Foo
95+
{
96+
}
97+
";
98+
99+
var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1);
100+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
101+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
102+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
103+
}
104+
105+
/// <summary>
106+
/// Verifies that a used-defined replacement variable <c>fileName</c> works as expected.
107+
/// </summary>
108+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
109+
[Fact]
110+
public virtual async Task TestFileNameUserVariableAsync()
111+
{
112+
this.currentTestSettings = @"
113+
{
114+
""settings"": {
115+
""documentationRules"": {
116+
""companyName"": ""FooCorp"",
117+
""copyrightText"": ""{fileName} Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information."",
118+
""variables"": {
119+
""licenseName"": ""???"",
120+
""licenseFile"": ""LICENSE"",
121+
""fileName"": ""Not a file""
122+
},
123+
""xmlHeader"": false
124+
}
125+
}
126+
}
127+
";
128+
129+
var testCode = @"namespace Foo
130+
{
131+
}
132+
";
133+
var fixedCode = @"// Not a file Copyright (c) FooCorp. All rights reserved.
134+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
135+
51136
namespace Foo
52137
{
53138
}
@@ -191,7 +276,7 @@ namespace Bar
191276
/// <inheritdoc/>
192277
protected override string GetSettings()
193278
{
194-
return TestSettings;
279+
return this.currentTestSettings;
195280
}
196281

197282
/// <inheritdoc/>

StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void VerifySettingsDefaults()
2525
var styleCopSettings = SettingsHelper.GetStyleCopSettings(default(SyntaxTreeAnalysisContext), CancellationToken.None);
2626

2727
Assert.Equal("PlaceholderCompany", styleCopSettings.DocumentationRules.CompanyName);
28-
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.CopyrightText);
28+
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
2929
Assert.True(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
3030
Assert.Equal(0, styleCopSettings.NamingRules.AllowedHungarianPrefixes.Length);
3131

@@ -69,7 +69,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
6969
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
7070

7171
Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName);
72-
Assert.Equal("Custom copyright text.", styleCopSettings.DocumentationRules.CopyrightText);
72+
Assert.Equal("Custom copyright text.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
7373
Assert.False(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
7474
Assert.Equal(new[] { "a", "ab" }, styleCopSettings.NamingRules.AllowedHungarianPrefixes);
7575

@@ -98,7 +98,7 @@ public async Task VerifySettingsWillUseCompanyNameInDefaultCopyrightTextAsync()
9898
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
9999

100100
Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName);
101-
Assert.Equal("Copyright (c) TestCompany. All rights reserved.", styleCopSettings.DocumentationRules.CopyrightText);
101+
Assert.Equal("Copyright (c) TestCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
102102
}
103103

104104
[Fact]
@@ -117,7 +117,7 @@ public async Task VerifyCircularReferenceBehaviorAsync()
117117

118118
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
119119

120-
Assert.Equal("[CircularReference]", styleCopSettings.DocumentationRules.CopyrightText);
120+
Assert.Equal("[CircularReference]", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
121121
}
122122

123123
[Fact]
@@ -136,7 +136,7 @@ public async Task VerifyInvalidReferenceBehaviorAsync()
136136

137137
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
138138

139-
Assert.Equal("[InvalidReference]", styleCopSettings.DocumentationRules.CopyrightText);
139+
Assert.Equal("[InvalidReference]", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
140140
}
141141

142142
[Fact]
@@ -149,7 +149,7 @@ public async Task VerifyInvalidJsonBehaviorAsync()
149149

150150
// The result is the same as the default settings.
151151
Assert.Equal("PlaceholderCompany", styleCopSettings.DocumentationRules.CompanyName);
152-
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.CopyrightText);
152+
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
153153
}
154154

155155
private static async Task<SyntaxTreeAnalysisContext> CreateAnalysisContextAsync(string stylecopJSON)

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderAnalyzers.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS
247247
return;
248248
}
249249

250-
if (!CompareCopyrightText(settings.DocumentationRules, fileHeader.CopyrightText))
250+
if (!CompareCopyrightText(context, settings.DocumentationRules, fileHeader.CopyrightText))
251251
{
252252
context.ReportDiagnostic(Diagnostic.Create(SA1636Descriptor, fileHeader.GetLocation(context.Tree)));
253253
return;
@@ -319,15 +319,16 @@ private static void CheckCopyrightText(SyntaxTreeAnalysisContext context, Docume
319319
return;
320320
}
321321

322-
var settingsCopyrightText = documentationSettings.CopyrightText;
322+
string fileName = Path.GetFileName(context.Tree.FilePath);
323+
var settingsCopyrightText = documentationSettings.GetCopyrightText(fileName);
323324
if (string.Equals(settingsCopyrightText, DocumentationSettings.DefaultCopyrightText, StringComparison.OrdinalIgnoreCase))
324325
{
325326
// The copyright text is meaningless until the company name is configured by the user.
326327
return;
327328
}
328329

329330
// trim any leading / trailing new line or whitespace characters (those are a result of the XML formatting)
330-
if (!CompareCopyrightText(documentationSettings, copyrightText.Trim('\r', '\n', ' ', '\t')))
331+
if (!CompareCopyrightText(context, documentationSettings, copyrightText.Trim('\r', '\n', ' ', '\t')))
331332
{
332333
var location = fileHeader.GetElementLocation(context.Tree, copyrightElement);
333334
context.ReportDiagnostic(Diagnostic.Create(SA1636Descriptor, location));
@@ -378,10 +379,11 @@ private static void CheckSummaryHeader(SyntaxTreeAnalysisContext context, Compil
378379
}
379380
}
380381

381-
private static bool CompareCopyrightText(DocumentationSettings documentationSettings, string copyrightText)
382+
private static bool CompareCopyrightText(SyntaxTreeAnalysisContext context, DocumentationSettings documentationSettings, string copyrightText)
382383
{
383384
// make sure that both \n and \r\n are accepted from the settings.
384-
var reformattedCopyrightTextParts = documentationSettings.CopyrightText.Replace("\r\n", "\n").Split('\n');
385+
string fileName = Path.GetFileName(context.Tree.FilePath);
386+
var reformattedCopyrightTextParts = documentationSettings.GetCopyrightText(fileName).Replace("\r\n", "\n").Split('\n');
385387
var fileHeaderCopyrightTextParts = copyrightText.Replace("\r\n", "\n").Split('\n');
386388

387389
if (reformattedCopyrightTextParts.Length != fileHeaderCopyrightTextParts.Length)

StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/DocumentationSettings.cs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace StyleCop.Analyzers.Settings.ObjectModel
55
{
6+
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Text.RegularExpressions;
89
using Newtonsoft.Json;
@@ -16,7 +17,7 @@ internal class DocumentationSettings
1617
internal const string DefaultCompanyName = "PlaceholderCompany";
1718

1819
/// <summary>
19-
/// The default value for the <see cref="CopyrightText"/> property.
20+
/// The default value for the <see cref="GetCopyrightText(string)"/> method.
2021
/// </summary>
2122
internal const string DefaultCopyrightText = "Copyright (c) {companyName}. All rights reserved.";
2223

@@ -27,13 +28,13 @@ internal class DocumentationSettings
2728
private string companyName;
2829

2930
/// <summary>
30-
/// This is the backing field for the <see cref="CopyrightText"/> property.
31+
/// This is the backing field for the <see cref="GetCopyrightText(string)"/> method.
3132
/// </summary>
3233
[JsonProperty("copyrightText", DefaultValueHandling = DefaultValueHandling.Ignore)]
3334
private string copyrightText;
3435

3536
/// <summary>
36-
/// This is the cache for the <see cref="CopyrightText"/> property.
37+
/// This is the cache for the <see cref="GetCopyrightText(string)"/> method.
3738
/// </summary>
3839
private string copyrightTextCache;
3940

@@ -120,19 +121,6 @@ public string CompanyName
120121
}
121122
}
122123

123-
public string CopyrightText
124-
{
125-
get
126-
{
127-
if (this.copyrightTextCache == null)
128-
{
129-
this.copyrightTextCache = this.BuildCopyrightText();
130-
}
131-
132-
return this.copyrightTextCache;
133-
}
134-
}
135-
136124
public string HeaderDecoration
137125
{
138126
get
@@ -175,8 +163,28 @@ public bool XmlHeader
175163
public FileNamingConvention FileNamingConvention =>
176164
this.fileNamingConvention;
177165

178-
private string BuildCopyrightText()
166+
public string GetCopyrightText(string fileName)
167+
{
168+
string copyrightText = this.copyrightTextCache;
169+
if (copyrightText != null)
170+
{
171+
return copyrightText;
172+
}
173+
174+
var expandedCopyrightText = this.BuildCopyrightText(fileName);
175+
if (!expandedCopyrightText.Value)
176+
{
177+
// Unable to cache the copyright text due to use of a {fileName} variable.
178+
return expandedCopyrightText.Key;
179+
}
180+
181+
this.copyrightTextCache = expandedCopyrightText.Key;
182+
return this.copyrightTextCache;
183+
}
184+
185+
private KeyValuePair<string, bool> BuildCopyrightText(string fileName)
179186
{
187+
bool canCache = true;
180188
string pattern = Regex.Escape("{") + "(?<Property>[a-zA-Z0-9]+)" + Regex.Escape("}");
181189
MatchEvaluator evaluator =
182190
match =>
@@ -197,13 +205,22 @@ private string BuildCopyrightText()
197205
return value;
198206
}
199207

208+
if (key == "fileName")
209+
{
210+
// The 'fileName' built-in variable is only applied when the user did not include an
211+
// explicit value for a custom 'fileName' variable.
212+
canCache = false;
213+
return fileName;
214+
}
215+
200216
break;
201217
}
202218

203219
return "[InvalidReference]";
204220
};
205221

206-
return Regex.Replace(this.copyrightText, pattern, evaluator);
222+
string expanded = Regex.Replace(this.copyrightText, pattern, evaluator);
223+
return new KeyValuePair<string, bool>(expanded, canCache);
207224
}
208225
}
209226
}

StyleCopAnalyzers.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{A0840AD0
3434
EndProject
3535
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentation", "{19DD9E9D-877A-4492-9B7F-2E681DD58308}"
3636
ProjectSection(SolutionItems) = preProject
37+
documentation\AlternativeRules.md = documentation\AlternativeRules.md
3738
documentation\Configuration.md = documentation\Configuration.md
39+
documentation\DocumentationRules.md = documentation\DocumentationRules.md
3840
documentation\DotNetCli.md = documentation\DotNetCli.md
3941
documentation\EnableConfiguration.md = documentation\EnableConfiguration.md
4042
documentation\KnownChanges.md = documentation\KnownChanges.md
43+
documentation\LayoutRules.md = documentation\LayoutRules.md
44+
documentation\MaintainabilityRules.md = documentation\MaintainabilityRules.md
45+
documentation\NamingRules.md = documentation\NamingRules.md
46+
documentation\OrderingRules.md = documentation\OrderingRules.md
47+
documentation\ReadabilityRules.md = documentation\ReadabilityRules.md
4148
documentation\SA0000Roslyn7446Workaround.md = documentation\SA0000Roslyn7446Workaround.md
4249
documentation\SA0001.md = documentation\SA0001.md
4350
documentation\SA0002.md = documentation\SA0002.md
@@ -225,6 +232,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat
225232
documentation\SA1650.md = documentation\SA1650.md
226233
documentation\SA1651.md = documentation\SA1651.md
227234
documentation\SA1652.md = documentation\SA1652.md
235+
documentation\SpacingRules.md = documentation\SpacingRules.md
236+
documentation\SpecialRules.md = documentation\SpecialRules.md
228237
documentation\SX1101.md = documentation\SX1101.md
229238
documentation\SX1309.md = documentation\SX1309.md
230239
documentation\SX1309S.md = documentation\SX1309S.md

0 commit comments

Comments
 (0)