Skip to content

Commit ba5970f

Browse files
Edit config and view merged config commands
1 parent 10fc5cc commit ba5970f

23 files changed

+606
-22
lines changed

src/Bicep.Core.UnitTests/Configuration/PatchingConfigurationManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ public PatchingConfigurationManager(ConfigurationManager configurationManager, F
1717
}
1818

1919
public RootConfiguration GetConfiguration(Uri sourceFileUri) => patchFunc(configurationManager.GetConfiguration(sourceFileUri));
20+
21+
public void PurgeCache() => configurationManager.PurgeCache();
2022
}

src/Bicep.Core.UnitTests/Extensions/StringExtensionsTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Text;
88
using System.Threading.Tasks;
9+
using Bicep.Core.Extensions;
910
using FluentAssertions;
1011
using Microsoft.VisualStudio.TestTools.UnitTesting;
1112

@@ -98,5 +99,21 @@ public void ExtractRegexGroups_AllGroups_Invalid(string s, string regex, string
9899
var ex = Assert.ThrowsException<Exception>(() => s.ExtractRegexGroups(regex));
99100
ex.Message.Should().Be(nameof(StringExtensions.ExtractRegexGroups) + ": " + expectedError);
100101
}
102+
103+
[DataTestMethod]
104+
[DataRow("line1\nline2", 4, ' ', " line1\n line2")]
105+
[DataRow("line1\nline2", 4, ' ', " line1\n line2")]
106+
[DataRow("line1\nline2", 2, '\t', "\t\tline1\n\t\tline2")]
107+
[DataRow("", 4, '-', "----")]
108+
[DataRow("single line", 3, '-', "---single line")]
109+
[DataRow(" single line", 3, '-', "--- single line")]
110+
[DataRow("a\nb", 2, '-', "--a\n--b")]
111+
[DataRow("a\r\nb", 2, '-', "--a\r\n--b")]
112+
[DataRow("a\r\n\nb", 2, '-', "--a\r\n--\n--b")]
113+
public void IndentLines_ShouldIndentCorrectly(string input, int indent, char indentChar, string expected)
114+
{
115+
var result = input.IndentLines(indent, indentChar);
116+
result.Should().Be(expected);
117+
}
101118
}
102119
}

src/Bicep.Core/Analyzers/Interfaces/IBicepAnalyzer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using Bicep.Core.Configuration;
45
using Bicep.Core.Diagnostics;
56
using Bicep.Core.Semantics;
67

78
namespace Bicep.Core.Analyzers.Interfaces
89
{
910
public interface IBicepAnalyzer
1011
{
12+
string AnalyzerName { get; }
13+
14+
bool IsEnabled(AnalyzersConfiguration configuration);
15+
1116
IEnumerable<IBicepAnalyzerRule> GetRuleSet();
1217
IEnumerable<IDiagnostic> Analyze(SemanticModel model);
1318
}

src/Bicep.Core/Analyzers/Interfaces/IBicepAnalyzerRule.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using Bicep.Core.Configuration;
45
using Bicep.Core.Diagnostics;
56
using Bicep.Core.Semantics;
67
using Microsoft.Extensions.DependencyInjection;
@@ -26,6 +27,8 @@ public interface IBicepAnalyzerRule
2627
DiagnosticStyling DiagnosticStyling { get; }
2728
Uri? Uri { get; }
2829

30+
DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration);
31+
2932
IEnumerable<IDiagnostic> Analyze(SemanticModel model, IServiceProvider serviceProvider);
3033
}
3134
}

src/Bicep.Core/Analyzers/Linter/LinterAnalyzer.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ namespace Bicep.Core.Analyzers.Linter
1515
{
1616
public class LinterAnalyzer : IBicepAnalyzer
1717
{
18-
public const string AnalyzerName = "core";
18+
public const string Name = "core";
1919

20-
public static string LinterEnabledSetting => $"{AnalyzerName}.enabled";
20+
public string AnalyzerName => Name;
2121

22-
public static string LinterVerboseSetting => $"{AnalyzerName}.verbose";
22+
public static string LinterEnabledSetting => $"{Name}.enabled";
23+
24+
public static string LinterVerboseSetting => $"{Name}.verbose";
2325

2426
private readonly LinterRulesProvider linterRulesProvider;
2527

@@ -34,9 +36,9 @@ public LinterAnalyzer(IServiceProvider serviceProvider)
3436
this.serviceProvider = serviceProvider;
3537
}
3638

37-
private bool LinterEnabled(SemanticModel model) => model.Configuration.Analyzers.GetValue(LinterEnabledSetting, false); // defaults to true in base bicepconfig.json file
39+
public bool IsEnabled(AnalyzersConfiguration configuration) => configuration.GetValue(LinterEnabledSetting, false); // defaults to true in base bicepconfig.json file
3840

39-
private bool LinterVerbose(SemanticModel model) => model.Configuration.Analyzers.GetValue(LinterVerboseSetting, false);
41+
private bool IsVerbose(AnalyzersConfiguration configuration) => configuration.GetValue(LinterVerboseSetting, false);
4042

4143

4244
[UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "List of types comes from a source generator.")]
@@ -60,10 +62,10 @@ public IEnumerable<IDiagnostic> Analyze(SemanticModel semanticModel)
6062
{
6163
var diagnostics = new List<IDiagnostic>();
6264

63-
if (this.LinterEnabled(semanticModel))
65+
if (this.IsEnabled(semanticModel.Configuration.Analyzers))
6466
{
6567
// add an info diagnostic for local configuration reporting
66-
if (this.LinterVerbose(semanticModel))
68+
if (this.IsVerbose(semanticModel.Configuration.Analyzers))
6769
{
6870
diagnostics.Add(GetConfigurationDiagnostic(semanticModel));
6971
}
@@ -72,7 +74,7 @@ public IEnumerable<IDiagnostic> Analyze(SemanticModel semanticModel)
7274
}
7375
else
7476
{
75-
if (this.LinterVerbose(semanticModel))
77+
if (this.IsVerbose(semanticModel.Configuration.Analyzers))
7678
{
7779
diagnostics.Add(new Diagnostic(
7880
TextSpan.TextDocumentStart,

src/Bicep.Core/Analyzers/Linter/LinterRuleBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public LinterRuleBase(
3535

3636
public LinterRuleCategory Category { get; }
3737

38-
public readonly string RuleConfigSection = $"{LinterAnalyzer.AnalyzerName}.rules";
38+
public readonly string RuleConfigSection = $"{LinterAnalyzer.Name}.rules";
3939

4040
public DiagnosticLevel DefaultDiagnosticLevel =>
4141
OverrideCategoryDefaultDiagnosticLevel.HasValue ? OverrideCategoryDefaultDiagnosticLevel.Value : GetDefaultDiagosticLevelForCategory(this.Category);
@@ -96,7 +96,7 @@ public virtual IEnumerable<IDiagnostic> AnalyzeInternal(SemanticModel model, Dia
9696

9797
protected DiagnosticLevel GetDiagnosticLevel(SemanticModel model) => GetDiagnosticLevel(model.Configuration.Analyzers);
9898

99-
protected DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration)
99+
public DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration)
100100
{
101101
if (GetConfigurationValue(configuration, "level", DefaultDiagnosticLevel.ToString()) is string configuredLevel && Enum.TryParse<DiagnosticLevel>(configuredLevel, true, out var parsed))
102102
{

src/Bicep.Core/Configuration/IConfigurationManager.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public interface IConfigurationManager
1919
/// <returns>The configuration for the source file.</returns>
2020
RootConfiguration GetConfiguration(Uri sourceFileUri);
2121

22+
void PurgeCache();
23+
2224
/// <summary>
2325
/// Gets the built-in configuration.
2426
/// </summary>
@@ -55,6 +57,8 @@ internal ConstantConfigurationManager(RootConfiguration configuration)
5557
}
5658

5759
public RootConfiguration GetConfiguration(Uri sourceFileUri) => configuration;
60+
61+
public void PurgeCache() { }
5862
}
5963
}
6064
}

src/Bicep.Core/Extensions/StringExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Text.RegularExpressions;
5+
46
namespace Bicep.Core.Extensions;
57

68
public static class StringExtensions
@@ -48,4 +50,8 @@ public static string UppercaseFirstLetter(this string input)
4850
}
4951
}
5052

53+
public static string IndentLines(this string input, int indent, char indentChar = ' ')
54+
{
55+
return new Regex("(^)", RegexOptions.Multiline).Replace(input, "$1" + new string(indentChar, indent));
56+
}
5157
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using Bicep.Core.Analyzers.Interfaces;
6+
using Bicep.Core.Analyzers.Linter;
7+
using Bicep.Core.Configuration;
8+
using Bicep.Core.UnitTests;
9+
using Bicep.Core.UnitTests.Assertions;
10+
using Bicep.LanguageServer.Handlers;
11+
using FluentAssertions;
12+
using Microsoft.VisualStudio.TestTools.UnitTesting;
13+
14+
namespace Bicep.LangServer.UnitTests.Handlers;
15+
16+
[TestClass]
17+
public class BicepGetConfigInfoHandlerTests
18+
{
19+
[NotNull]
20+
public TestContext? TestContext { get; set; }
21+
22+
private RootConfiguration TestConfig = IConfigurationManager.GetBuiltInConfiguration().WithAnalyzersConfiguration(new AnalyzersConfiguration("""
23+
{
24+
"core": {
25+
"enabled": true,
26+
"rules": {
27+
"artifacts-parameters": {
28+
"level": "info"
29+
},
30+
"max-asserts": {
31+
"level": "error"
32+
},
33+
"use-recent-api-versions":{
34+
"level": "warning",
35+
"maxAgeInDays": 30
36+
},
37+
"max-variables":{
38+
"level": "off"
39+
}
40+
}
41+
}
42+
}
43+
"""));
44+
45+
[TestMethod]
46+
public async Task MergedConfiguration()
47+
{
48+
var handler = new BicepGetConfigInfoHandler(new LinterAnalyzer(BicepTestConstants.EmptyServiceProvider), IConfigurationManager.WithStaticConfiguration(TestConfig));
49+
50+
var result = await handler.Handle(new BicepGetConfigInfoParams("file:///main.bicep"), default);
51+
result.ConfigPath.Should().BeNull();
52+
53+
result.EffectiveConfig.Should().ContainIgnoringNewlines("""
54+
"formatting": {
55+
"indentKind": "Space",
56+
"newlineKind": "LF",
57+
"insertFinalNewline": true,
58+
"indentSize": 2,
59+
"width": 120
60+
}
61+
""".ReplaceLineEndings());
62+
result.EffectiveConfig.Should().ContainIgnoringNewlines("""
63+
"analyzers": {
64+
"core": {
65+
"enabled": true,
66+
"rules": {
67+
"artifacts-parameters": {
68+
"level": "info"
69+
},
70+
"max-asserts": {
71+
"level": "error"
72+
},
73+
"use-recent-api-versions": {
74+
"level": "warning",
75+
"maxAgeInDays": 30
76+
},
77+
"max-variables": {
78+
"level": "off"
79+
}
80+
}
81+
}
82+
}
83+
""".ReplaceLineEndings());
84+
85+
}
86+
87+
[TestMethod]
88+
public async Task LinterState()
89+
{
90+
var handler = new BicepGetConfigInfoHandler(new LinterAnalyzer(BicepTestConstants.EmptyServiceProvider), IConfigurationManager.WithStaticConfiguration(TestConfig));
91+
92+
var result = await handler.Handle(new BicepGetConfigInfoParams("file:///main.bicep"), default);
93+
result.ConfigPath.Should().BeNull();
94+
95+
// Example output:
96+
/*
97+
Analyzer enabled: True
98+
99+
Enabled rules:
100+
adminusername-should-not-be-literal: Warning (default)
101+
artifacts-parameters: Warning (default)
102+
decompiler-cleanup: Warning (default)
103+
max-asserts: Error (default)
104+
max-outputs: Error (default)
105+
max-params: Error (default)
106+
max-resources: Error (default)
107+
max-variables: Error (default)
108+
no-conflicting-metadata: Warning (default)
109+
no-deployments-resources: Warning (default)
110+
no-hardcoded-env-urls: Warning (default)
111+
nested-deployment-template-scoping: Error (default)
112+
no-unnecessary-dependson: Warning (default)
113+
no-unused-existing-resources: Warning (default)
114+
no-unused-params: Warning (default)
115+
no-unused-vars: Warning (default)
116+
outputs-should-not-contain-secrets: Warning (default)
117+
prefer-interpolation: Warning (default)
118+
prefer-unquoted-property-names: Warning (default)
119+
protect-commandtoexecute-secrets: Warning (default)
120+
secure-secrets-in-params: Warning (default)
121+
secure-parameter-default: Warning (default)
122+
secure-params-in-nested-deploy: Warning (default)
123+
simplify-interpolation: Warning (default)
124+
simplify-json-null: Warning (default)
125+
use-parent-property: Warning (default)
126+
use-resource-id-functions: Warning (default)
127+
use-resource-symbol-reference: Warning (default)
128+
use-safe-access: Warning (default)
129+
use-secure-value-for-secure-inputs: Warning (default)
130+
use-stable-resource-identifiers: Warning (default)
131+
use-stable-vm-image: Warning (default)
132+
133+
Disabled rules:
134+
explicit-values-for-loc-params: Off (default)
135+
no-hardcoded-location: Off (default)
136+
no-loc-expr-outside-params: Off (default)
137+
use-recent-api-versions: Off (default)
138+
use-recent-module-versions: Off (default)
139+
use-user-defined-types: Off (default)
140+
what-if-short-circuiting: Off (default)
141+
142+
*/
143+
144+
result.LinterState.Should().StartWith("Analyzer enabled: True");
145+
}
146+
147+
[TestMethod]
148+
public async Task IfAnalyzerDisabled_ThenAllRulesOff()
149+
{
150+
var config = IConfigurationManager.GetBuiltInConfiguration().WithAnalyzersConfiguration(new AnalyzersConfiguration("""
151+
{
152+
"analyzers": {
153+
"core": {
154+
"enabled": false
155+
}
156+
}
157+
}
158+
"""));
159+
var handler = new BicepGetConfigInfoHandler(new LinterAnalyzer(BicepTestConstants.EmptyServiceProvider), IConfigurationManager.WithStaticConfiguration(config));
160+
161+
var result = await handler.Handle(new BicepGetConfigInfoParams("file:///main.bicep"), default);
162+
var linterState = result.LinterState.ReplaceLineEndings();
163+
164+
linterState.Should().ContainIgnoringNewlines("Analyzer enabled: False");
165+
linterState.Should().ContainIgnoringNewlines("""
166+
Enabled rules:
167+
168+
Disabled rules:
169+
adminusername-should-not-be-literal: Off (analyzer disabled)
170+
""");
171+
}
172+
173+
174+
}

src/Bicep.LangServer/Handlers/BicepCreateConfigFileHandler.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ public class BicepCreateConfigFileHandler : ExecuteTypedResponseCommandHandlerBa
2727
{
2828
private readonly ILogger<BicepCreateConfigFileHandler> logger;
2929
private readonly IClientCapabilitiesProvider clientCapabilitiesProvider;
30+
private readonly IConfigurationManager configurationManager;
3031
private readonly ILanguageServerFacade server;
3132

32-
public BicepCreateConfigFileHandler(ILanguageServerFacade server, IClientCapabilitiesProvider clientCapabilitiesProvider, ILogger<BicepCreateConfigFileHandler> logger, ISerializer serializer)
33-
: base(LangServerConstants.CreateConfigFile, serializer)
33+
public BicepCreateConfigFileHandler(ILanguageServerFacade server, IClientCapabilitiesProvider clientCapabilitiesProvider, IConfigurationManager configurationManager, ILogger<BicepCreateConfigFileHandler> logger, ISerializer serializer)
34+
: base(LangServerConstants.CreateConfigFileCommand, serializer)
3435
{
3536
this.clientCapabilitiesProvider = clientCapabilitiesProvider;
37+
this.configurationManager = configurationManager;
3638
this.server = server;
3739
this.logger = logger;
3840
}
@@ -48,6 +50,7 @@ public override async Task<bool> Handle(BicepCreateConfigParams request, Cancell
4850
this.logger.LogTrace($"Writing new configuration file to {destinationPath}");
4951
string defaultBicepConfig = DefaultBicepConfigHelper.GetDefaultBicepConfig();
5052
await File.WriteAllTextAsync(destinationPath, defaultBicepConfig);
53+
this.configurationManager.PurgeCache();
5154

5255
await BicepEditLinterRuleCommandHandler.AddAndSelectRuleLevel(server, clientCapabilitiesProvider, destinationPath, DefaultBicepConfigHelper.DefaultRuleCode);
5356
return true;

0 commit comments

Comments
 (0)