Skip to content

Commit bd2f647

Browse files
committed
Merge pull request #2032 from sharwell/fix-2029
Implement SA0002 (InvalidSettingsFile)
2 parents 33d6540 + 41b5dc3 commit bd2f647

13 files changed

Lines changed: 295 additions & 10 deletions

File tree

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ namespace StyleCop.Analyzers.Settings
2323
[Shared]
2424
internal class SettingsFileCodeFixProvider : CodeFixProvider
2525
{
26-
private const string StyleCopSettingsFileName = "stylecop.json";
27-
private const string DefaultSettingsFileContent = @"{
26+
internal const string DefaultSettingsFileContent = @"{
2827
// ACTION REQUIRED: This file was automatically added to your project, but it
2928
// will not take effect until additional steps are taken to enable it. See the
3029
// following page for additional information:
@@ -40,6 +39,8 @@ internal class SettingsFileCodeFixProvider : CodeFixProvider
4039
}
4140
";
4241

42+
private const string StyleCopSettingsFileName = "stylecop.json";
43+
4344
/// <inheritdoc/>
4445
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
4546
ImmutableArray.Create(

StyleCop.Analyzers/StyleCop.Analyzers.Test/SpecialRules/SA0001UnitTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public async Task TestDisabledDocumentationModesAsync(DocumentationMode document
4242
}
4343
";
4444

45-
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(null, 0, 0);
45+
// This diagnostic is reported without a location
46+
DiagnosticResult expected = this.CSharpDiagnostic();
4647

4748
this.documentationMode = documentationMode;
4849
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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.SpecialRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Analyzers.Settings;
10+
using Analyzers.SpecialRules;
11+
using Microsoft.CodeAnalysis.Diagnostics;
12+
using TestHelper;
13+
using Xunit;
14+
15+
/// <summary>
16+
/// Unit tests for <see cref="SA0002InvalidSettingsFile"/>.
17+
/// </summary>
18+
public class SA0002UnitTests : DiagnosticVerifier
19+
{
20+
private const string TestCode = @"
21+
namespace NamespaceName { }
22+
";
23+
24+
private string settings;
25+
26+
[Fact]
27+
public async Task TestMissingSettingsAsync()
28+
{
29+
await this.VerifyCSharpDiagnosticAsync(TestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
30+
}
31+
32+
[Fact]
33+
public async Task TestValidSettingsAsync()
34+
{
35+
this.settings = SettingsFileCodeFixProvider.DefaultSettingsFileContent;
36+
37+
await this.VerifyCSharpDiagnosticAsync(TestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
38+
}
39+
40+
[Fact]
41+
public async Task TestInvalidSettingsAsync()
42+
{
43+
this.settings = @"
44+
{
45+
""$schema"": ""https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json""
46+
""settings"": {
47+
""documentationRules"": {
48+
""companyName"": ""ACME, Inc"",
49+
""copyrightText"": ""Copyright 2015 {companyName}. All rights reserved.""
50+
}
51+
}
52+
}
53+
";
54+
55+
// This diagnostic is reported without a location
56+
DiagnosticResult expected = this.CSharpDiagnostic();
57+
58+
await this.VerifyCSharpDiagnosticAsync(TestCode, expected, CancellationToken.None).ConfigureAwait(false);
59+
}
60+
61+
/// <inheritdoc/>
62+
protected override string GetSettings()
63+
{
64+
return this.settings;
65+
}
66+
67+
/// <inheritdoc/>
68+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
69+
{
70+
yield return new SA0002InvalidSettingsFile();
71+
}
72+
}
73+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@
360360
<Compile Include="SpacingRules\SA1027UnitTests.cs" />
361361
<Compile Include="SpacingRules\SA1028UnitTests.cs" />
362362
<Compile Include="SpecialRules\SA0001UnitTests.cs" />
363+
<Compile Include="SpecialRules\SA0002UnitTests.cs" />
363364
<Compile Include="Verifiers\CodeFixVerifier.cs" />
364365
<Compile Include="Verifiers\DiagnosticVerifier.cs" />
365366
</ItemGroup>

StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/DiagnosticVerifier.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,26 @@ private static string FormatDiagnostics(ImmutableArray<DiagnosticAnalyzer> analy
326326
return builder.ToString();
327327
}
328328

329+
private static bool IsSubjectToExclusion(DiagnosticResult result)
330+
{
331+
if (result.Id.StartsWith("CS", StringComparison.Ordinal))
332+
{
333+
return false;
334+
}
335+
336+
if (result.Id.StartsWith("AD", StringComparison.Ordinal))
337+
{
338+
return false;
339+
}
340+
341+
if (result.Locations.Length == 0)
342+
{
343+
return false;
344+
}
345+
346+
return true;
347+
}
348+
329349
/// <summary>
330350
/// General method that gets a collection of actual <see cref="Diagnostic"/>s found in the source after the
331351
/// analyzer is run, then verifies each of them.
@@ -346,12 +366,13 @@ private async Task VerifyDiagnosticsAsync(string[] sources, string language, Imm
346366
if (filenames == null)
347367
{
348368
// Also check if the analyzer honors exclusions
349-
if (expected.Any(x => x.Id.StartsWith("SA", StringComparison.Ordinal) || x.Id.StartsWith("SX", StringComparison.Ordinal)))
369+
if (expected.Any(IsSubjectToExclusion))
350370
{
351-
// We want to look at non-stylecop diagnostics only. We also insert a new line at the beginning
352-
// so we have to move all diagnostic location down by one line
371+
// Diagnostics reported by the compiler and analyzer diagnostics which don't have a location will
372+
// still be reported. We also insert a new line at the beginning so we have to move all diagnostic
373+
// locations which have a specific position down by one line.
353374
var expectedResults = expected
354-
.Where(x => !x.Id.StartsWith("SA", StringComparison.Ordinal) && !x.Id.StartsWith("SX", StringComparison.Ordinal))
375+
.Where(x => !IsSubjectToExclusion(x))
355376
.Select(x => x.WithLineOffset(1))
356377
.ToArray();
357378

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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
5+
{
6+
using Newtonsoft.Json;
7+
using StyleCop.Analyzers.Settings.ObjectModel;
8+
9+
/// <summary>
10+
/// Defines the behavior of various <see cref="SettingsHelper"/> methods in the event of a deserialization error.
11+
/// </summary>
12+
internal enum DeserializationFailureBehavior
13+
{
14+
/// <summary>
15+
/// When deserialization fails, return a default <see cref="StyleCopSettings"/> instance.
16+
/// </summary>
17+
ReturnDefaultSettings,
18+
19+
/// <summary>
20+
/// When deserialization fails, throw a <see cref="JsonException"/> containing details about the error.
21+
/// </summary>
22+
ThrowException
23+
}
24+
}

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ static SettingsHelper()
4141
/// <summary>
4242
/// Gets the StyleCop settings.
4343
/// </summary>
44+
/// <remarks>
45+
/// <para>If a <see cref="JsonException"/> occurs while deserializing the settings file, a default settings
46+
/// instance is returned.</para>
47+
/// </remarks>
4448
/// <param name="context">The context that will be used to determine the StyleCop settings.</param>
4549
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
4650
/// <returns>A <see cref="StyleCopSettings"/> instance that represents the StyleCop settings for the given context.</returns>
@@ -52,15 +56,32 @@ internal static StyleCopSettings GetStyleCopSettings(this SyntaxTreeAnalysisCont
5256
/// <summary>
5357
/// Gets the StyleCop settings.
5458
/// </summary>
59+
/// <remarks>
60+
/// <para>If a <see cref="JsonException"/> occurs while deserializing the settings file, a default settings
61+
/// instance is returned.</para>
62+
/// </remarks>
5563
/// <param name="options">The analyzer options that will be used to determine the StyleCop settings.</param>
5664
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
5765
/// <returns>A <see cref="StyleCopSettings"/> instance that represents the StyleCop settings for the given context.</returns>
5866
internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, CancellationToken cancellationToken)
5967
{
60-
return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create<AdditionalText>(), cancellationToken);
68+
return GetStyleCopSettings(options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken);
6169
}
6270

63-
private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalText> additionalFiles, CancellationToken cancellationToken)
71+
/// <summary>
72+
/// Gets the StyleCop settings.
73+
/// </summary>
74+
/// <param name="options">The analyzer options that will be used to determine the StyleCop settings.</param>
75+
/// <param name="failureBehavior">The behavior of the method when a <see cref="JsonException"/> occurs while
76+
/// deserializing the settings file.</param>
77+
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
78+
/// <returns>A <see cref="StyleCopSettings"/> instance that represents the StyleCop settings for the given context.</returns>
79+
internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
80+
{
81+
return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create<AdditionalText>(), failureBehavior, cancellationToken);
82+
}
83+
84+
private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalText> additionalFiles, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken)
6485
{
6586
try
6687
{
@@ -74,7 +95,7 @@ private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalTex
7495
}
7596
}
7697
}
77-
catch (JsonException)
98+
catch (JsonException) when (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings)
7899
{
79100
// The settings file is invalid -> return the default settings.
80101
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.SpecialRules
5+
{
6+
using System;
7+
using System.Collections.Immutable;
8+
using System.Globalization;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using Newtonsoft.Json;
12+
13+
/// <summary>
14+
/// The <em>stylecop.json</em> settings file could not be loaded due to a deserialization failure.
15+
/// </summary>
16+
[NoCodeFix("No automatic code fix is possible for general JSON syntax errors.")]
17+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
18+
internal class SA0002InvalidSettingsFile : DiagnosticAnalyzer
19+
{
20+
/// <summary>
21+
/// The ID for diagnostics produced by the <see cref="SA0002InvalidSettingsFile"/> analyzer.
22+
/// </summary>
23+
public const string DiagnosticId = "SA0002";
24+
private const string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0002.md";
25+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(SpecialResources.SA0002Title), SpecialResources.ResourceManager, typeof(SpecialResources));
26+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(SpecialResources.SA0002MessageFormat), SpecialResources.ResourceManager, typeof(SpecialResources));
27+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(SpecialResources.SA0002Description), SpecialResources.ResourceManager, typeof(SpecialResources));
28+
29+
private static readonly DiagnosticDescriptor Descriptor =
30+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.SpecialRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
31+
32+
private static readonly Action<CompilationAnalysisContext> CompilationAction = HandleCompilation;
33+
34+
/// <inheritdoc/>
35+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
36+
ImmutableArray.Create(Descriptor);
37+
38+
/// <inheritdoc/>
39+
public override void Initialize(AnalysisContext context)
40+
{
41+
context.RegisterCompilationAction(CompilationAction);
42+
}
43+
44+
private static void HandleCompilation(CompilationAnalysisContext context)
45+
{
46+
try
47+
{
48+
SettingsHelper.GetStyleCopSettings(context.Options, DeserializationFailureBehavior.ThrowException, context.CancellationToken);
49+
}
50+
catch (JsonException ex)
51+
{
52+
string details = ex.Message;
53+
string completeDescription = string.Format(Description.ToString(CultureInfo.CurrentCulture), details);
54+
55+
var completeDescriptor = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.SpecialRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, completeDescription, HelpLink);
56+
context.ReportDiagnostic(Diagnostic.Create(completeDescriptor, Location.None));
57+
}
58+
}
59+
}
60+
}

StyleCop.Analyzers/StyleCop.Analyzers/SpecialRules/SpecialResources.Designer.cs

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/SpecialRules/SpecialResources.resx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,15 @@ Note that some situations are not affected by the bug:
142142
<data name="SA0001Title" xml:space="preserve">
143143
<value>XML comment analysis disabled</value>
144144
</data>
145+
<data name="SA0002Description" xml:space="preserve">
146+
<value>Various errors in the stylecop.json file can prevent the file from being loaded by the analyzers. In this case, the default settings are used instead.
147+
148+
{0}</value>
149+
</data>
150+
<data name="SA0002MessageFormat" xml:space="preserve">
151+
<value>The stylecop.json settings file could not be loaded</value>
152+
</data>
153+
<data name="SA0002Title" xml:space="preserve">
154+
<value>Invalid settings file</value>
155+
</data>
145156
</root>

0 commit comments

Comments
 (0)