Skip to content

Add edit config and view merged config commands #16943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public PatchingConfigurationManager(ConfigurationManager configurationManager, F
}

public RootConfiguration GetConfiguration(Uri sourceFileUri) => patchFunc(configurationManager.GetConfiguration(sourceFileUri));

public void PurgeCache() => configurationManager.PurgeCache();
}
17 changes: 17 additions & 0 deletions src/Bicep.Core.UnitTests/Extensions/StringExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Bicep.Core.Extensions;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand Down Expand Up @@ -98,5 +99,21 @@ public void ExtractRegexGroups_AllGroups_Invalid(string s, string regex, string
var ex = Assert.ThrowsException<Exception>(() => s.ExtractRegexGroups(regex));
ex.Message.Should().Be(nameof(StringExtensions.ExtractRegexGroups) + ": " + expectedError);
}

[DataTestMethod]
[DataRow("line1\nline2", 4, ' ', " line1\n line2")]
[DataRow("line1\nline2", 4, ' ', " line1\n line2")]
[DataRow("line1\nline2", 2, '\t', "\t\tline1\n\t\tline2")]
[DataRow("", 4, '-', "----")]
[DataRow("single line", 3, '-', "---single line")]
[DataRow(" single line", 3, '-', "--- single line")]
[DataRow("a\nb", 2, '-', "--a\n--b")]
[DataRow("a\r\nb", 2, '-', "--a\r\n--b")]
[DataRow("a\r\n\nb", 2, '-', "--a\r\n--\n--b")]
public void IndentLines_ShouldIndentCorrectly(string input, int indent, char indentChar, string expected)
{
var result = input.IndentLines(indent, indentChar);
result.Should().Be(expected);
}
}
}
5 changes: 5 additions & 0 deletions src/Bicep.Core/Analyzers/Interfaces/IBicepAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.Core.Configuration;
using Bicep.Core.Diagnostics;
using Bicep.Core.Semantics;

namespace Bicep.Core.Analyzers.Interfaces
{
public interface IBicepAnalyzer
{
string AnalyzerName { get; }

bool IsEnabled(AnalyzersConfiguration configuration);

IEnumerable<IBicepAnalyzerRule> GetRuleSet();
IEnumerable<IDiagnostic> Analyze(SemanticModel model);
}
Expand Down
3 changes: 3 additions & 0 deletions src/Bicep.Core/Analyzers/Interfaces/IBicepAnalyzerRule.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.Core.Configuration;
using Bicep.Core.Diagnostics;
using Bicep.Core.Semantics;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -26,6 +27,8 @@ public interface IBicepAnalyzerRule
DiagnosticStyling DiagnosticStyling { get; }
Uri? Uri { get; }

DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration);

IEnumerable<IDiagnostic> Analyze(SemanticModel model, IServiceProvider serviceProvider);
}
}
18 changes: 10 additions & 8 deletions src/Bicep.Core/Analyzers/Linter/LinterAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ namespace Bicep.Core.Analyzers.Linter
{
public class LinterAnalyzer : IBicepAnalyzer
{
public const string AnalyzerName = "core";
public const string Name = "core";

public static string LinterEnabledSetting => $"{AnalyzerName}.enabled";
public string AnalyzerName => Name;

public static string LinterVerboseSetting => $"{AnalyzerName}.verbose";
public static string LinterEnabledSetting => $"{Name}.enabled";

public static string LinterVerboseSetting => $"{Name}.verbose";

private readonly LinterRulesProvider linterRulesProvider;

Expand All @@ -34,9 +36,9 @@ public LinterAnalyzer(IServiceProvider serviceProvider)
this.serviceProvider = serviceProvider;
}

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

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


[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.")]
Expand All @@ -60,10 +62,10 @@ public IEnumerable<IDiagnostic> Analyze(SemanticModel semanticModel)
{
var diagnostics = new List<IDiagnostic>();

if (this.LinterEnabled(semanticModel))
if (this.IsEnabled(semanticModel.Configuration.Analyzers))
{
// add an info diagnostic for local configuration reporting
if (this.LinterVerbose(semanticModel))
if (this.IsVerbose(semanticModel.Configuration.Analyzers))
{
diagnostics.Add(GetConfigurationDiagnostic(semanticModel));
}
Expand All @@ -72,7 +74,7 @@ public IEnumerable<IDiagnostic> Analyze(SemanticModel semanticModel)
}
else
{
if (this.LinterVerbose(semanticModel))
if (this.IsVerbose(semanticModel.Configuration.Analyzers))
{
diagnostics.Add(new Diagnostic(
TextSpan.TextDocumentStart,
Expand Down
4 changes: 2 additions & 2 deletions src/Bicep.Core/Analyzers/Linter/LinterRuleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public LinterRuleBase(

public LinterRuleCategory Category { get; }

public readonly string RuleConfigSection = $"{LinterAnalyzer.AnalyzerName}.rules";
public readonly string RuleConfigSection = $"{LinterAnalyzer.Name}.rules";

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

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

protected DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration)
public DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration)
{
if (GetConfigurationValue(configuration, "level", DefaultDiagnosticLevel.ToString()) is string configuredLevel && Enum.TryParse<DiagnosticLevel>(configuredLevel, true, out var parsed))
{
Expand Down
4 changes: 4 additions & 0 deletions src/Bicep.Core/Configuration/IConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface IConfigurationManager
/// <returns>The configuration for the source file.</returns>
RootConfiguration GetConfiguration(Uri sourceFileUri);

void PurgeCache();

/// <summary>
/// Gets the built-in configuration.
/// </summary>
Expand Down Expand Up @@ -55,6 +57,8 @@ internal ConstantConfigurationManager(RootConfiguration configuration)
}

public RootConfiguration GetConfiguration(Uri sourceFileUri) => configuration;

public void PurgeCache() { }
}
}
}
6 changes: 6 additions & 0 deletions src/Bicep.Core/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.RegularExpressions;

namespace Bicep.Core.Extensions;

public static class StringExtensions
Expand Down Expand Up @@ -48,4 +50,8 @@ public static string UppercaseFirstLetter(this string input)
}
}

public static string IndentLines(this string input, int indent, char indentChar = ' ')
{
return new Regex("(^)", RegexOptions.Multiline).Replace(input, "$1" + new string(indentChar, indent));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using Bicep.Core.Analyzers.Interfaces;
using Bicep.Core.Analyzers.Linter;
using Bicep.Core.Configuration;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.LanguageServer.Handlers;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Bicep.LangServer.UnitTests.Handlers;

[TestClass]
public class BicepGetConfigInfoHandlerTests
{
[NotNull]
public TestContext? TestContext { get; set; }

private RootConfiguration TestConfig = IConfigurationManager.GetBuiltInConfiguration().WithAnalyzersConfiguration(new AnalyzersConfiguration("""
{
"core": {
"enabled": true,
"rules": {
"artifacts-parameters": {
"level": "info"
},
"max-asserts": {
"level": "error"
},
"use-recent-api-versions":{
"level": "warning",
"maxAgeInDays": 30
},
"max-variables":{
"level": "off"
}
}
}
}
"""));

[TestMethod]
public async Task MergedConfiguration()
{
var handler = new BicepGetConfigInfoHandler(new LinterAnalyzer(BicepTestConstants.EmptyServiceProvider), IConfigurationManager.WithStaticConfiguration(TestConfig));

var result = await handler.Handle(new BicepGetConfigInfoParams("file:///main.bicep"), default);
result.ConfigPath.Should().BeNull();

result.EffectiveConfig.Should().ContainIgnoringNewlines("""
"formatting": {
"indentKind": "Space",
"newlineKind": "LF",
"insertFinalNewline": true,
"indentSize": 2,
"width": 120
}
""".ReplaceLineEndings());
result.EffectiveConfig.Should().ContainIgnoringNewlines("""
"analyzers": {
"core": {
"enabled": true,
"rules": {
"artifacts-parameters": {
"level": "info"
},
"max-asserts": {
"level": "error"
},
"use-recent-api-versions": {
"level": "warning",
"maxAgeInDays": 30
},
"max-variables": {
"level": "off"
}
}
}
}
""".ReplaceLineEndings());

}

[TestMethod]
public async Task LinterState()
{
var handler = new BicepGetConfigInfoHandler(new LinterAnalyzer(BicepTestConstants.EmptyServiceProvider), IConfigurationManager.WithStaticConfiguration(TestConfig));

var result = await handler.Handle(new BicepGetConfigInfoParams("file:///main.bicep"), default);
result.ConfigPath.Should().BeNull();

// Example output:
/*
Analyzer enabled: True

Enabled rules:
adminusername-should-not-be-literal: Warning (default)
artifacts-parameters: Warning (default)
decompiler-cleanup: Warning (default)
max-asserts: Error (default)
max-outputs: Error (default)
max-params: Error (default)
max-resources: Error (default)
max-variables: Error (default)
no-conflicting-metadata: Warning (default)
no-deployments-resources: Warning (default)
no-hardcoded-env-urls: Warning (default)
nested-deployment-template-scoping: Error (default)
no-unnecessary-dependson: Warning (default)
no-unused-existing-resources: Warning (default)
no-unused-params: Warning (default)
no-unused-vars: Warning (default)
outputs-should-not-contain-secrets: Warning (default)
prefer-interpolation: Warning (default)
prefer-unquoted-property-names: Warning (default)
protect-commandtoexecute-secrets: Warning (default)
secure-secrets-in-params: Warning (default)
secure-parameter-default: Warning (default)
secure-params-in-nested-deploy: Warning (default)
simplify-interpolation: Warning (default)
simplify-json-null: Warning (default)
use-parent-property: Warning (default)
use-resource-id-functions: Warning (default)
use-resource-symbol-reference: Warning (default)
use-safe-access: Warning (default)
use-secure-value-for-secure-inputs: Warning (default)
use-stable-resource-identifiers: Warning (default)
use-stable-vm-image: Warning (default)

Disabled rules:
explicit-values-for-loc-params: Off (default)
no-hardcoded-location: Off (default)
no-loc-expr-outside-params: Off (default)
use-recent-api-versions: Off (default)
use-recent-module-versions: Off (default)
use-user-defined-types: Off (default)
what-if-short-circuiting: Off (default)

*/

result.LinterState.Should().StartWith("Analyzer enabled: True");
}

[TestMethod]
public async Task IfAnalyzerDisabled_ThenAllRulesOff()
{
var config = IConfigurationManager.GetBuiltInConfiguration().WithAnalyzersConfiguration(new AnalyzersConfiguration("""
{
"analyzers": {
"core": {
"enabled": false
}
}
}
"""));
var handler = new BicepGetConfigInfoHandler(new LinterAnalyzer(BicepTestConstants.EmptyServiceProvider), IConfigurationManager.WithStaticConfiguration(config));

var result = await handler.Handle(new BicepGetConfigInfoParams("file:///main.bicep"), default);
var linterState = result.LinterState.ReplaceLineEndings();

linterState.Should().ContainIgnoringNewlines("Analyzer enabled: False");
linterState.Should().ContainIgnoringNewlines("""
Enabled rules:

Disabled rules:
adminusername-should-not-be-literal: Off (analyzer disabled)
""");
}


}
7 changes: 5 additions & 2 deletions src/Bicep.LangServer/Handlers/BicepCreateConfigFileHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ public class BicepCreateConfigFileHandler : ExecuteTypedResponseCommandHandlerBa
{
private readonly ILogger<BicepCreateConfigFileHandler> logger;
private readonly IClientCapabilitiesProvider clientCapabilitiesProvider;
private readonly IConfigurationManager configurationManager;
private readonly ILanguageServerFacade server;

public BicepCreateConfigFileHandler(ILanguageServerFacade server, IClientCapabilitiesProvider clientCapabilitiesProvider, ILogger<BicepCreateConfigFileHandler> logger, ISerializer serializer)
: base(LangServerConstants.CreateConfigFile, serializer)
public BicepCreateConfigFileHandler(ILanguageServerFacade server, IClientCapabilitiesProvider clientCapabilitiesProvider, IConfigurationManager configurationManager, ILogger<BicepCreateConfigFileHandler> logger, ISerializer serializer)
: base(LangServerConstants.CreateConfigFileCommand, serializer)
{
this.clientCapabilitiesProvider = clientCapabilitiesProvider;
this.configurationManager = configurationManager;
this.server = server;
this.logger = logger;
}
Expand All @@ -48,6 +50,7 @@ public override async Task<bool> Handle(BicepCreateConfigParams request, Cancell
this.logger.LogTrace($"Writing new configuration file to {destinationPath}");
string defaultBicepConfig = DefaultBicepConfigHelper.GetDefaultBicepConfig();
await File.WriteAllTextAsync(destinationPath, defaultBicepConfig);
this.configurationManager.PurgeCache();

await BicepEditLinterRuleCommandHandler.AddAndSelectRuleLevel(server, clientCapabilitiesProvider, destinationPath, DefaultBicepConfigHelper.DefaultRuleCode);
return true;
Expand Down
Loading
Loading