Skip to content
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

Fix exception thrown in CLI when bicepconfig.json is invalid #4348

Merged
merged 7 commits into from
Sep 20, 2021
Merged
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
76 changes: 76 additions & 0 deletions src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using FluentAssertions.Execution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
Expand Down Expand Up @@ -286,6 +287,81 @@ public async Task Build_LockedOutputFile_ShouldProduceExpectedError()
}
}

[TestMethod]
public async Task Build_WithEmptyBicepConfig_ShouldProduceOutputFile()
{
string testOutputPath = Path.Combine(TestContext.ResultsDirectory, Guid.NewGuid().ToString());
var inputFile = FileHelper.SaveResultFile(this.TestContext, "main.bicep", DataSets.Empty.Bicep, testOutputPath);
FileHelper.SaveResultFile(this.TestContext, "bicepconfig.json", string.Empty, testOutputPath);

var (output, error, result) = await Bicep("build", inputFile);

result.Should().Be(0);
output.Should().BeEmpty();
error.Should().BeEmpty();

var expectedOutputFile = Path.Combine(testOutputPath, "main.json");

File.Exists(expectedOutputFile).Should().BeTrue();
}

[TestMethod]
public async Task Build_WithInvalidBicepConfig_ShouldProduceOutputFile()
{
string testOutputPath = Path.Combine(TestContext.ResultsDirectory, Guid.NewGuid().ToString());
var inputFile = FileHelper.SaveResultFile(this.TestContext, "main.bicep", DataSets.Empty.Bicep, testOutputPath);
FileHelper.SaveResultFile(this.TestContext, "bicepconfig.json", @"{
""analyzers"": {
""core"": {
""verbose"": false,
""enabled"": true,
""rules"": {
""no-unused-params"": {
""level"": ""info""
", testOutputPath);

var (output, error, result) = await Bicep("build", inputFile);

result.Should().Be(0);
output.Should().BeEmpty();
error.Should().BeEmpty();

var expectedOutputFile = Path.Combine(testOutputPath, "main.json");

File.Exists(expectedOutputFile).Should().BeTrue();
}

[TestMethod]
public async Task Build_WithValidBicepConfig_ShouldProduceOutputFileAndExpectedError()
{
string testOutputPath = Path.Combine(TestContext.ResultsDirectory, Guid.NewGuid().ToString());
var inputFile = FileHelper.SaveResultFile(this.TestContext, "main.bicep", @"param storageAccountName string = 'test'", testOutputPath);
FileHelper.SaveResultFile(this.TestContext, "bicepconfig.json", @"{
""analyzers"": {
""core"": {
""verbose"": false,
""enabled"": true,
""rules"": {
""no-unused-params"": {
""level"": ""warning""
}
}
}
}
}", testOutputPath);

var expectedOutputFile = Path.Combine(testOutputPath, "main.json");

File.Exists(expectedOutputFile).Should().BeFalse();

var (output, error, result) = await Bicep("build", "--outdir", testOutputPath, inputFile);

File.Exists(expectedOutputFile).Should().BeTrue();
result.Should().Be(0);
output.Should().BeEmpty();
error.Should().Contain(@"main.bicep(1,7) : Warning no-unused-params: Parameter ""storageAccountName"" is declared but never used. [https://aka.ms/bicep/linter/no-unused-params]");
}

private static IEnumerable<object[]> GetValidDataSets() => DataSets
.AllDataSets
.Where(ds => ds.IsValid)
Expand Down
35 changes: 32 additions & 3 deletions src/Bicep.Cli/Services/CompilationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// Licensed under the MIT License.

using Bicep.Cli.Logging;
using Bicep.Core.Configuration;
using Bicep.Core.Extensions;
using Bicep.Core.FileSystem;
using Bicep.Core.Registry;
using Bicep.Core.Semantics;
using Bicep.Core.TypeSystem.Az;
using Bicep.Core.Workspaces;
using Bicep.Decompiler;
using System;
using System.Collections.Immutable;
using System.IO;
using System.Threading.Tasks;

namespace Bicep.Cli.Services
Expand All @@ -23,7 +26,7 @@ public class CompilationService
private readonly Workspace workspace;
private readonly TemplateDecompiler decompiler;

public CompilationService(IDiagnosticLogger diagnosticLogger, IFileResolver fileResolver, InvocationContext invocationContext, IModuleDispatcher moduleDispatcher, TemplateDecompiler decompiler)
public CompilationService(IDiagnosticLogger diagnosticLogger, IFileResolver fileResolver, InvocationContext invocationContext, IModuleDispatcher moduleDispatcher, TemplateDecompiler decompiler)
{
this.diagnosticLogger = diagnosticLogger;
this.fileResolver = fileResolver;
Expand Down Expand Up @@ -61,13 +64,39 @@ public async Task<Compilation> CompileAsync(string inputPath, bool skipRestore)
}
}

var compilation = new Compilation(this.invocationContext.ResourceTypeProvider, sourceFileGrouping, null);
ConfigHelper configHelper = GetConfigHelper(inputUri);
var compilation = new Compilation(this.invocationContext.ResourceTypeProvider, sourceFileGrouping, configHelper);

LogDiagnostics(compilation);

return compilation;
}

private ConfigHelper GetConfigHelper(Uri uri)
{
ConfigHelper configHelper;

try
{
configHelper = new ConfigHelper(Path.GetDirectoryName(uri.LocalPath), fileResolver);
}
catch (Exception ex)
{
var invocationContext = new InvocationContext(
AzResourceTypeProvider.CreateWithAzTypes(),
Console.Out,
Console.Error,
ThisAssembly.AssemblyFileVersion,
features: null,
clientFactory: null);
invocationContext.OutputWriter.WriteLine(ex.Message);

configHelper = new ConfigHelper(null, fileResolver, useDefaultConfig: true).GetDisabledLinterConfig();
}

return configHelper;
}

public async Task<(Uri, ImmutableDictionary<Uri, string>)> DecompileAsync(string inputPath, string outputPath)
{
inputPath = PathHelper.ResolvePath(inputPath);
Expand All @@ -83,7 +112,7 @@ public async Task<Compilation> CompileAsync(string inputPath, bool skipRestore)
}

// to verify success we recompile and check for syntax errors.
await CompileAsync(decompilation.entrypointUri.AbsolutePath, skipRestore: true);
await CompileAsync(decompilation.entrypointUri.AbsolutePath, skipRestore: true);

return decompilation;
}
Expand Down
2 changes: 0 additions & 2 deletions src/Bicep.Core.IntegrationTests/RegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Bicep.Core.Configuration;
using Bicep.Core.Diagnostics;
using Bicep.Core.Features;
using Bicep.Core.FileSystem;
Expand All @@ -15,7 +14,6 @@
using Bicep.Core.TypeSystem.Az;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Configuration;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.Workspaces;
using FluentAssertions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Bicep.Core.Semantics;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Configuration;
using Bicep.Core.UnitTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Bicep.Core.TypeSystem;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Configuration;
using Bicep.Core.UnitTests.Utils;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down
1 change: 0 additions & 1 deletion src/Bicep.Core.Samples/ExamplesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Bicep.Core.Configuration;
using Bicep.Core.UnitTests.Configuration;
using Bicep.Core.Registry;

namespace Bicep.Core.Samples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using Moq;
using Bicep.Core.FileSystem;
using Bicep.Core.Configuration;
using Bicep.Core.UnitTests.Configuration;

namespace Bicep.Core.UnitTests.TypeSystem.Az
{
Expand Down
8 changes: 5 additions & 3 deletions src/Bicep.Core/Configuration/ConfigHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ public class ConfigHelper
{
private const string bicepConfigResourceName = "Bicep.Core.Configuration.bicepconfig.json";
private readonly IFileResolver fileResolver;
private readonly bool useDefaultConfig;

/// <summary>
/// Property exposes the configuration root
/// that is currently loaded
/// </summary>
public IConfigurationRoot Config { get; private set; }

public ConfigHelper(string? localFolder, IFileResolver fileResolver)
public ConfigHelper(string? localFolder, IFileResolver fileResolver, bool useDefaultConfig = false)
{
this.fileResolver = fileResolver;
this.useDefaultConfig = useDefaultConfig;

if (localFolder is not null)
{
Expand Down Expand Up @@ -56,7 +58,7 @@ private IConfigurationRoot BuildConfig(string? localFolder)
configBuilder.AddJsonStream(defaultConfigStream);

// last added json settings take precedent - add local settings last
if (DiscoverLocalConfigurationFile(localFolder) is string localConfig)
if (!useDefaultConfig && DiscoverLocalConfigurationFile(localFolder) is string localConfig)
{
// we must set reloadOnChange to false here - if it set to true, then ConfigurationBuilder will initialize
// a FileSystem.Watcher instance - which has severe performance impact on non-Windows OSes (https://github.com/dotnet/runtime/issues/42036)
Expand All @@ -82,7 +84,7 @@ private IConfigurationRoot BuildConfig(string? localFolder)
catch (Exception ex)
{
throw new Exception(
string.Format("Could not load configuration file. {0}", ex.InnerException?.Message ?? ex.Message));
string.Format("Could not load configuration file. {0} Please fix errors in the following configuration file: {1}", ex.InnerException?.Message ?? ex.Message, this.CustomSettingsFileName));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@
// Licensed under the MIT License.

using Bicep.Core.Analyzers.Linter;
using Bicep.Core.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bicep.Core.UnitTests.Configuration
namespace Bicep.Core.Configuration
{
public static class ConfigHelperExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using Bicep.Decompiler.Exceptions;
using Bicep.Decompiler;
using Bicep.Core.Configuration;
using Bicep.Core.UnitTests.Configuration;
using Bicep.Core.Registry;
using Bicep.Core.UnitTests;

Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.LangServer.IntegrationTests/BicepConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ private async Task VerifyDiagnosticsAsync(MultipleMessageListener<PublishDiagnos
diagsParams.Diagnostics.Should().SatisfyRespectively(
x =>
{
x.Message.Should().Be(message);
x.Message.Should().Contain(message);
x.Severity.Should().Be(diagnosticSeverity);
x.Code?.String.Should().Be(code);
x.Range.Should().Be(new Range
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Bicep.Core.Samples;
using Bicep.Core.Semantics;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Configuration;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.Workspaces;
using Bicep.LanguageServer.Providers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void RefreshCompilationOfSourceFilesInWorkspace_WithInvalidBicepConfigFil
diagnostics.Should().SatisfyRespectively(
x =>
{
x.Message.Should().Be("Could not load configuration file. Expected depth to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed. LineNumber: 8 | BytePositionInLine: 13.");
x.Message.Should().Contain("Could not load configuration file. Expected depth to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed. LineNumber: 8 | BytePositionInLine: 13.");
x.Severity.Should().Be(DiagnosticSeverity.Error);
x.Code?.String.Should().Be("Fatal");
x.Range.Should().Be(new Range
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Bicep.Core.TypeSystem;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Configuration;
using Bicep.LanguageServer.Completions;
using Bicep.LanguageServer.Snippets;
using FluentAssertions;
Expand Down
4 changes: 3 additions & 1 deletion src/Bicep.LangServer/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ private static void RegisterServices(CreationOptions creationOptions, IServiceCo
// without manually constructing up the graph
services.AddSingleton<EmitterSettings>(services => new EmitterSettings(creationOptions.AssemblyFileVersion ?? ThisAssembly.AssemblyFileVersion, enableSymbolicNames: featureProvider.SymbolicNameCodegenEnabled));
services.AddSingleton<IResourceTypeProvider>(services => creationOptions.ResourceTypeProvider ?? AzResourceTypeProvider.CreateWithAzTypes());
services.AddSingleton<ISnippetsProvider>(services => creationOptions.SnippetsProvider ?? new SnippetsProvider(fileResolver, new ConfigHelper(null, new FileResolver())));
// We'll use default bicepconfig.json settings during SnippetsProvider creation to avoid errors during language service initialization.
// We don't do any validation in SnippetsProvider. So using default settings shouldn't be a problem.
services.AddSingleton<ISnippetsProvider>(services => creationOptions.SnippetsProvider ?? new SnippetsProvider(fileResolver, new ConfigHelper(null, new FileResolver(), useDefaultConfig: true)));
services.AddSingleton<IFileResolver>(services => fileResolver);
services.AddSingleton<IFeatureProvider>(services => creationOptions.Features ?? new FeatureProvider());
services.AddSingleton<IModuleRegistryProvider, DefaultModuleRegistryProvider>();
Expand Down