Skip to content

Commit 715f8cc

Browse files
authored
Merge pull request #2635 from dotarj/feature-dotfile-config
support .stylecop.json configuration file name
2 parents c725402 + f388c2e commit 715f8cc

7 files changed

Lines changed: 90 additions & 17 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Settings/SettingsFileCodeFixProvider.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
6363
var workspace = project.Solution.Workspace;
6464

6565
// check if the settings file already exists
66-
if (project.AdditionalDocuments.Any(IsStyleCopSettingsDocument))
66+
if (project.AdditionalDocuments.Any(document => SettingsHelper.IsStyleCopSettingsFile(document.Name)))
6767
{
6868
return SpecializedTasks.CompletedTask;
6969
}
@@ -94,11 +94,6 @@ public override FixAllProvider GetFixAllProvider()
9494
return null;
9595
}
9696

97-
private static bool IsStyleCopSettingsDocument(TextDocument document)
98-
{
99-
return string.Equals(document.Name, SettingsHelper.SettingsFileName, StringComparison.OrdinalIgnoreCase);
100-
}
101-
10297
private static Task<Solution> GetTransformedSolutionAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
10398
{
10499
var project = document.Project;

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language)
179179
if (!string.IsNullOrEmpty(settings))
180180
{
181181
var documentId = DocumentId.CreateNewId(projectId);
182-
solution = solution.AddAdditionalDocument(documentId, SettingsHelper.SettingsFileName, settings);
182+
solution = solution.AddAdditionalDocument(documentId, this.GetSettingsFileName(), settings);
183183
}
184184

185185
ParseOptions parseOptions = solution.GetProject(projectId).ParseOptions;
@@ -204,6 +204,15 @@ protected virtual string GetSettings()
204204
return null;
205205
}
206206

207+
/// <summary>
208+
/// Gets the name of the settings file to use.
209+
/// </summary>
210+
/// <returns>The name of the settings file to use.</returns>
211+
protected virtual string GetSettingsFileName()
212+
{
213+
return SettingsHelper.SettingsFileName;
214+
}
215+
207216
protected DiagnosticResult CSharpDiagnostic(string diagnosticId = null)
208217
{
209218
var analyzers = this.GetCSharpDiagnosticAnalyzers();

StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsFileCodeFixProviderUnitTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace NamespaceName
2626
";
2727

2828
private bool createSettingsFile;
29+
private string settingsFileName = SettingsHelper.SettingsFileName;
2930

3031
/// <summary>
3132
/// Verifies that a file without a header, but with leading trivia will produce the correct diagnostic message.
@@ -64,6 +65,21 @@ public async Task TestSettingsFileDoesNotExistAsync()
6465
public async Task TestSettingsFileAlreadyExistsAsync()
6566
{
6667
this.createSettingsFile = true;
68+
this.settingsFileName = SettingsHelper.SettingsFileName;
69+
70+
var offeredFixes = await this.GetOfferedCSharpFixesAsync(TestCode).ConfigureAwait(false);
71+
Assert.Empty(offeredFixes);
72+
}
73+
74+
/// <summary>
75+
/// Verifies that a code fix will not be offered if the settings file is already present.
76+
/// </summary>
77+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
78+
[Fact]
79+
public async Task TestDotPrefixedSettingsFileAlreadyExistsAsync()
80+
{
81+
this.createSettingsFile = true;
82+
this.settingsFileName = SettingsHelper.AltSettingsFileName;
6783

6884
var offeredFixes = await this.GetOfferedCSharpFixesAsync(TestCode).ConfigureAwait(false);
6985
Assert.Empty(offeredFixes);
@@ -80,6 +96,12 @@ protected override string GetSettings()
8096
return null;
8197
}
8298

99+
/// <inheritdoc/>
100+
protected override string GetSettingsFileName()
101+
{
102+
return this.settingsFileName;
103+
}
104+
83105
/// <inheritdoc/>
84106
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
85107
{

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,25 @@ public async Task VerifySettingsSupportsTrailingCommasAsync()
308308
Assert.Equal("a", styleCopSettings.NamingRules.AllowedHungarianPrefixes[0]);
309309
}
310310

311+
[Fact]
312+
public async Task VerifySettingsFileNameSupportsDotPrefixAsync()
313+
{
314+
var settings = @"
315+
{
316+
""settings"": {
317+
""documentationRules"": {
318+
""companyName"": ""TestCompany"",
319+
},
320+
}
321+
}
322+
";
323+
var context = await CreateAnalysisContextAsync(settings, ".stylecop.json").ConfigureAwait(false);
324+
325+
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
326+
327+
Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName);
328+
}
329+
311330
[Fact]
312331
public async Task VerifyInvalidJsonBehaviorAsync()
313332
{
@@ -334,7 +353,7 @@ public async Task VerifyEmptyOrMissingFileAsync()
334353
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
335354
}
336355

337-
private static async Task<SyntaxTreeAnalysisContext> CreateAnalysisContextAsync(string stylecopJSON)
356+
private static async Task<SyntaxTreeAnalysisContext> CreateAnalysisContextAsync(string stylecopJSON, string settingsFileName = SettingsHelper.SettingsFileName)
338357
{
339358
var projectId = ProjectId.CreateNewId();
340359
var documentId = DocumentId.CreateNewId(projectId);
@@ -347,7 +366,7 @@ private static async Task<SyntaxTreeAnalysisContext> CreateAnalysisContextAsync(
347366
var document = solution.GetDocument(documentId);
348367
var syntaxTree = await document.GetSyntaxTreeAsync().ConfigureAwait(false);
349368

350-
var stylecopJSONFile = new AdditionalTextHelper(SettingsHelper.SettingsFileName, stylecopJSON);
369+
var stylecopJSONFile = new AdditionalTextHelper(settingsFileName, stylecopJSON);
351370
var additionalFiles = ImmutableArray.Create<AdditionalText>(stylecopJSONFile);
352371
var analyzerOptions = new AnalyzerOptions(additionalFiles);
353372

StyleCop.Analyzers/StyleCop.Analyzers.Test/SpecialRules/SA0002UnitTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ public void TestUnexpectedExceptionNotCaught()
278278
Assert.Null(additionalFiles[0].Path);
279279
Assert.Null(additionalFiles[0].GetText(CancellationToken.None));
280280
var context = new CompilationAnalysisContext(compilation: null, options: new AnalyzerOptions(additionalFiles), reportDiagnostic: null, isSupportedDiagnostic: null, cancellationToken: CancellationToken.None);
281-
Assert.Throws<NullReferenceException>(() => analysisContext.CompilationAction(context));
281+
Assert.Throws<ArgumentNullException>(() => analysisContext.CompilationAction(context));
282282
}
283283

284284
/// <inheritdoc/>

StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs

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

44
namespace StyleCop.Analyzers
55
{
6+
using System;
67
using System.Collections.Immutable;
78
using System.IO;
89
using System.Threading;
@@ -18,6 +19,7 @@ namespace StyleCop.Analyzers
1819
internal static class SettingsHelper
1920
{
2021
internal const string SettingsFileName = "stylecop.json";
22+
internal const string AltSettingsFileName = ".stylecop.json";
2123

2224
private static readonly SourceTextValueProvider<StyleCopSettings> SettingsValueProvider =
2325
new SourceTextValueProvider<StyleCopSettings>(
@@ -66,14 +68,33 @@ internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions option
6668
return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create<AdditionalText>(), failureBehavior, cancellationToken);
6769
}
6870

71+
/// <summary>
72+
/// Gets a value indicating whether the specified path points to a StyleCop settings file (stylecop.json or .stylecop.json).
73+
/// </summary>
74+
/// <param name="path">The path to test.</param>
75+
/// <returns><c>true</c> if <paramref name="path"/> points to a StyleCop settings file; otherwise, <c>false</c>.</returns>
76+
internal static bool IsStyleCopSettingsFile(string path)
77+
{
78+
if (path == null)
79+
{
80+
throw new ArgumentNullException(nameof(path));
81+
}
82+
83+
var fileName = Path.GetFileName(path);
84+
85+
return string.Equals(fileName, SettingsFileName, StringComparison.OrdinalIgnoreCase)
86+
|| string.Equals(fileName, AltSettingsFileName, StringComparison.OrdinalIgnoreCase);
87+
}
88+
6989
internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext context, AnalyzerOptions options, CancellationToken cancellationToken)
7090
{
7191
return GetStyleCopSettings(context, options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken);
7292
}
7393

7494
internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext context, AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
7595
{
76-
SourceText text = TryGetStyleCopSettingsText(options, cancellationToken);
96+
string settingsFilePath;
97+
SourceText text = TryGetStyleCopSettingsText(options, cancellationToken, out settingsFilePath);
7798
if (text == null)
7899
{
79100
return new StyleCopSettings();
@@ -90,7 +111,7 @@ internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext contex
90111
return settings;
91112
}
92113

93-
return GetStyleCopSettings(SettingsFileName, text, failureBehavior);
114+
return GetStyleCopSettings(settingsFilePath, text, failureBehavior);
94115
}
95116

96117
internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalysisContext context, AnalyzerOptions options, CancellationToken cancellationToken)
@@ -102,7 +123,8 @@ internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalys
102123
internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalysisContext context, AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
103124
#pragma warning restore RS1012 // Start action has no registered actions.
104125
{
105-
SourceText text = TryGetStyleCopSettingsText(options, cancellationToken);
126+
string settingsFilePath;
127+
SourceText text = TryGetStyleCopSettingsText(options, cancellationToken, out settingsFilePath);
106128
if (text == null)
107129
{
108130
return new StyleCopSettings();
@@ -119,7 +141,7 @@ internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalys
119141
return settings;
120142
}
121143

122-
return GetStyleCopSettings(SettingsFileName, text, failureBehavior);
144+
return GetStyleCopSettings(settingsFilePath, text, failureBehavior);
123145
}
124146

125147
private static StyleCopSettings GetStyleCopSettings(string path, SourceText text, DeserializationFailureBehavior failureBehavior)
@@ -159,24 +181,28 @@ private static StyleCopSettings GetStyleCopSettings(string path, SourceText text
159181
return new StyleCopSettings();
160182
}
161183

162-
private static SourceText TryGetStyleCopSettingsText(this AnalyzerOptions options, CancellationToken cancellationToken)
184+
private static SourceText TryGetStyleCopSettingsText(this AnalyzerOptions options, CancellationToken cancellationToken, out string settingsFilePath)
163185
{
164186
foreach (var additionalFile in options.AdditionalFiles)
165187
{
166-
if (Path.GetFileName(additionalFile.Path).ToLowerInvariant() == SettingsFileName)
188+
if (IsStyleCopSettingsFile(additionalFile.Path))
167189
{
190+
settingsFilePath = additionalFile.Path;
191+
168192
return additionalFile.GetText(cancellationToken);
169193
}
170194
}
171195

196+
settingsFilePath = null;
197+
172198
return null;
173199
}
174200

175201
private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalText> additionalFiles, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
176202
{
177203
foreach (var additionalFile in additionalFiles)
178204
{
179-
if (Path.GetFileName(additionalFile.Path).ToLowerInvariant() == SettingsFileName)
205+
if (IsStyleCopSettingsFile(additionalFile.Path))
180206
{
181207
SourceText additionalTextContent = additionalFile.GetText(cancellationToken);
182208
return GetStyleCopSettings(additionalFile.Path, additionalTextContent, failureBehavior);

documentation/Configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Code analysis rule sets are the standard way to configure most diagnostic analyz
1818

1919
The easiest way to add a **stylecop.json** configuration file to a new project is using a code fix provided by the project. To invoke the code fix, open any file where SA1633 is reported¹ and press Ctrl+. to bring up the Quick Fix menu. From the menu, select **Add StyleCop settings file to the project**.
2020

21+
The dot file naming convention is also supported, which makes it possible to name the configuration file **.stylecop.json**.
22+
2123
### JSON Schema for IntelliSense
2224

2325
A JSON schema is available for **stylecop.json**. By including a reference in **stylecop.json** to this schema, Visual Studio will offer IntelliSense functionality (code completion, quick info, etc.) while editing this file. The schema may be configured by adding the following top-level property in **stylecop.json**:

0 commit comments

Comments
 (0)