From c0ad57dff6c96bd5e9ef013b4d7c2caa3ef7a0c3 Mon Sep 17 00:00:00 2001 From: Anthony Martin <38542602+anthony-c-martin@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:35:49 -0500 Subject: [PATCH] Ensure language server refreshes modules on force refresh (#13278) Closes #13254 ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/13278) --- .../PublishCommandTests.cs | 4 +- .../PublishProviderCommandTests.cs | 2 +- .../DynamicAzTypesTests.cs | 8 +- .../ImportTests.cs | 4 +- .../RegistryProviderTests.cs | 10 +-- .../ResourceTypeProviderFactoryTests.cs | 4 +- src/Bicep.Core.Samples/DataSetsExtensions.cs | 63 +------------- .../Utils/RegistryHelper.cs | 85 +++++++++++++++++++ ...stContainerRegistryClientFactoryBuilder.cs | 4 +- .../Workspaces/SourceFileGrouping.cs | 4 +- .../LangServerScenarioTests.cs | 50 +++++++++++ .../BicepCompilationManager.cs | 7 ++ .../CompilationManager/ICompilationManager.cs | 2 + .../BicepForceModulesRestoreCommandHandler.cs | 20 +++-- 14 files changed, 182 insertions(+), 85 deletions(-) create mode 100644 src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs diff --git a/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs b/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs index c3b406fa29d..b013efd03a1 100644 --- a/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/PublishCommandTests.cs @@ -450,11 +450,11 @@ public async Task Publish_BicepModule_WithDescriptionAndDocUri_ShouldPlaceDescri var registryUri = new Uri($"https://{registryStr}"); var repository = $"test/{moduleName}".ToLowerInvariant(); - var (clientFactory, blobClients) = DataSetsExtensions.CreateMockRegistryClients((registryStr, repository)); + var (clientFactory, blobClients) = RegistryHelper.CreateMockRegistryClients((registryStr, repository)); var blobClient = blobClients[(registryUri, repository)]; - await DataSetsExtensions.PublishModuleToRegistryAsync(clientFactory, "modulename", $"br:example.com/test/{moduleName}:v1", bicepModuleContents, publishSource: false, documentationUri); + await RegistryHelper.PublishModuleToRegistry(clientFactory, "modulename", $"br:example.com/test/{moduleName}:v1", bicepModuleContents, publishSource: false, documentationUri); var manifest = blobClient.Manifests.Single().Value.ToObjectFromJson(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); diff --git a/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs b/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs index bec6a7c3c68..5228397ac99 100644 --- a/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs @@ -30,7 +30,7 @@ public async Task Publish_provider_should_succeed() var repository = $"test/provider"; var version = "0.0.1"; - var (clientFactory, blobClientMocks) = DataSetsExtensions.CreateMockRegistryClients((registryStr, repository)); + var (clientFactory, blobClientMocks) = RegistryHelper.CreateMockRegistryClients((registryStr, repository)); var mockBlobClient = blobClientMocks[(registryUri, repository)]; var indexPath = Path.Combine(outputDirectory, "index.json"); diff --git a/src/Bicep.Core.IntegrationTests/DynamicAzTypesTests.cs b/src/Bicep.Core.IntegrationTests/DynamicAzTypesTests.cs index 46bfe3d0fb5..7c2beca9a6d 100644 --- a/src/Bicep.Core.IntegrationTests/DynamicAzTypesTests.cs +++ b/src/Bicep.Core.IntegrationTests/DynamicAzTypesTests.cs @@ -33,9 +33,9 @@ private async Task GetServices() var services = new ServiceBuilder() .WithFeatureOverrides(new(ExtensibilityEnabled: true, DynamicTypeLoadingEnabled: true, CacheRootDirectory: cacheRoot)) - .WithContainerRegistryClientFactory(DataSetsExtensions.CreateOciClientForAzProvider()); + .WithContainerRegistryClientFactory(RegistryHelper.CreateOciClientForAzProvider()); - await DataSetsExtensions.PublishAzProvider(services.Build(), indexJson); + await RegistryHelper.PublishAzProvider(services.Build(), indexJson); return services; } @@ -125,12 +125,12 @@ public async Task Bicep_module_artifact_specified_in_provider_declaration_syntax { // ARRANGE var testArtifact = new ArtifactRegistryAddress(LanguageConstants.BicepPublicMcrRegistry, "bicep/providers/az", "0.2.661"); - var clientFactory = DataSetsExtensions.CreateMockRegistryClients((testArtifact.RegistryAddress, testArtifact.RepositoryPath)).factoryMock; + var clientFactory = RegistryHelper.CreateMockRegistryClients((testArtifact.RegistryAddress, testArtifact.RepositoryPath)).factoryMock; var services = new ServiceBuilder() .WithFeatureOverrides(new(ExtensibilityEnabled: true, DynamicTypeLoadingEnabled: true)) .WithContainerRegistryClientFactory(clientFactory); - await DataSetsExtensions.PublishModuleToRegistryAsync( + await RegistryHelper.PublishModuleToRegistry( clientFactory, moduleName: "az", target: testArtifact.ToSpecificationString(':'), diff --git a/src/Bicep.Core.IntegrationTests/ImportTests.cs b/src/Bicep.Core.IntegrationTests/ImportTests.cs index 2038a734ba4..1231f86acb1 100644 --- a/src/Bicep.Core.IntegrationTests/ImportTests.cs +++ b/src/Bicep.Core.IntegrationTests/ImportTests.cs @@ -35,11 +35,11 @@ private async Task GetServices() var services = new ServiceBuilder() .WithFeatureOverrides(new(ExtensibilityEnabled: true, DynamicTypeLoadingEnabled: true)) - .WithContainerRegistryClientFactory(DataSetsExtensions.CreateOciClientForAzProvider()) + .WithContainerRegistryClientFactory(RegistryHelper.CreateOciClientForAzProvider()) .WithMockFileSystem(fileSystem) .WithAzResourceTypeLoader(azTypeLoaderLazy.Value); - await DataSetsExtensions.PublishAzProvider(services.Build(), "/types/index.json"); + await RegistryHelper.PublishAzProvider(services.Build(), "/types/index.json"); return services; } diff --git a/src/Bicep.Core.IntegrationTests/RegistryProviderTests.cs b/src/Bicep.Core.IntegrationTests/RegistryProviderTests.cs index bfc441ed6e3..64822016223 100644 --- a/src/Bicep.Core.IntegrationTests/RegistryProviderTests.cs +++ b/src/Bicep.Core.IntegrationTests/RegistryProviderTests.cs @@ -17,7 +17,7 @@ public class RegistryProviderTests : TestBase { private static ServiceBuilder GetServiceBuilder(IFileSystem fileSystem, string registryHost, string repositoryPath, bool extensibilityEnabledBool, bool providerRegistryBool) { - var (clientFactory, _) = DataSetsExtensions.CreateMockRegistryClients((registryHost, repositoryPath)); + var clientFactory = RegistryHelper.CreateMockRegistryClient(registryHost, repositoryPath); return new ServiceBuilder() .WithFeatureOverrides(new(ExtensibilityEnabled: extensibilityEnabledBool, ProviderRegistry: providerRegistryBool)) @@ -38,7 +38,7 @@ public async Task Providers_published_to_a_registry_can_be_compiled() var services = GetServiceBuilder(fileSystem, registry, repository, true, true); - await DataSetsExtensions.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); + await RegistryHelper.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); var result = await CompilationHelper.RestoreAndCompile(services, """ provider 'br:example.azurecr.io/test/provider/http@1.2.3' @@ -69,7 +69,7 @@ public async Task Third_party_namespace_errors_with_configuration() var services = GetServiceBuilder(fileSystem, registry, repository, true, true); - await DataSetsExtensions.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); + await RegistryHelper.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); var result = await CompilationHelper.RestoreAndCompile(services, """ provider 'br:example.azurecr.io/test/provider/http@1.2.3' with {} @@ -101,7 +101,7 @@ public async Task Third_party_imports_are_enabled_when_feature_is_enabled() var services = GetServiceBuilder(fileSystem, registry, repository, true, true); - await DataSetsExtensions.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); + await RegistryHelper.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); var result = await CompilationHelper.RestoreAndCompile(services, @$" provider 'br:example.azurecr.io/test/provider/http@1.2.3' @@ -147,7 +147,7 @@ public async Task Third_party_imports_are_disabled_unless_feature_is_enabled() var services = GetServiceBuilder(fileSystem, registry, repository, false, false); - await DataSetsExtensions.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); + await RegistryHelper.PublishProviderToRegistryAsync(services.Build(), "/types/index.json", $"br:{registry}/{repository}:1.2.3"); var result = await CompilationHelper.RestoreAndCompile(services, @$" provider 'br:example.azurecr.io/test/provider/http@1.2.3' diff --git a/src/Bicep.Core.IntegrationTests/TypeSystem/Providers/ResourceTypeProviderFactoryTests.cs b/src/Bicep.Core.IntegrationTests/TypeSystem/Providers/ResourceTypeProviderFactoryTests.cs index f9e6a9d2374..03eaf66fedc 100644 --- a/src/Bicep.Core.IntegrationTests/TypeSystem/Providers/ResourceTypeProviderFactoryTests.cs +++ b/src/Bicep.Core.IntegrationTests/TypeSystem/Providers/ResourceTypeProviderFactoryTests.cs @@ -29,7 +29,7 @@ public async Task ProviderNameAndVersionAreUsedAsCacheKeys() var repositoryPath = $"test/provider"; var repositoryNames = new[] { "foo", "bar" }; - var (clientFactory, _) = DataSetsExtensions.CreateMockRegistryClients(repositoryNames.Select(name => (registry, $"{repositoryPath}/{name}")).ToArray()); + var (clientFactory, _) = RegistryHelper.CreateMockRegistryClients(repositoryNames.Select(name => (registry, $"{repositoryPath}/{name}")).ToArray()); var services = new ServiceBuilder() .WithFeatureOverrides(new(ExtensibilityEnabled: true, ProviderRegistry: true)) @@ -38,7 +38,7 @@ public async Task ProviderNameAndVersionAreUsedAsCacheKeys() foreach (var repoName in repositoryNames) { var indexJsonPath = Path.Combine(outputDirectory, "types", "index.json"); - await DataSetsExtensions.PublishProviderToRegistryAsync(services.Build(), indexJsonPath, $"br:{registry}/{repositoryPath}/{repoName}:1.2.3"); + await RegistryHelper.PublishProviderToRegistryAsync(services.Build(), indexJsonPath, $"br:{registry}/{repositoryPath}/{repoName}:1.2.3"); } var result = await CompilationHelper.RestoreAndCompile( diff --git a/src/Bicep.Core.Samples/DataSetsExtensions.cs b/src/Bicep.Core.Samples/DataSetsExtensions.cs index 8ec748b1af2..449e27c288b 100644 --- a/src/Bicep.Core.Samples/DataSetsExtensions.cs +++ b/src/Bicep.Core.Samples/DataSetsExtensions.cs @@ -76,20 +76,7 @@ public static IContainerRegistryClientFactory CreateMockRegistryClients(Immutabl clients.Add((targetReference.Registry, targetReference.Repository)); } - return CreateMockRegistryClients(clients.Concat(additionalClients).ToArray()).factoryMock; - } - - public static (IContainerRegistryClientFactory factoryMock, ImmutableDictionary<(Uri, string), MockRegistryBlobClient> blobClientMocks) CreateMockRegistryClients(params (string, string)[] clients) - { - var containerRegistryFactoryBuilder = new TestContainerRegistryClientFactoryBuilder(); - - foreach (var (registryHost, repository) in clients) - { - containerRegistryFactoryBuilder.RegisterMockRepositoryBlobClient(registryHost, repository); - - } - - return containerRegistryFactoryBuilder.Build(); + return RegistryHelper.CreateMockRegistryClients(clients.Concat(additionalClients).ToArray()).factoryMock; } public static ITemplateSpecRepositoryFactory CreateEmptyTemplateSpecRepositoryFactory(bool enablePublishSource = false) @@ -139,56 +126,10 @@ public static async Task PublishModulesToRegistryAsync(ImmutableDictionary s.WithDisabledAnalyzersConfiguration() - .AddSingleton(clientFactory) - .AddSingleton(BicepTestConstants.TemplateSpecRepositoryFactory) - .AddSingleton(featureProviderFactory) - ).Construct(); - - var targetReference = dispatcher.TryGetArtifactReference(ArtifactType.Module, target, RandomFileUri()).IsSuccess(out var @ref) ? @ref - : throw new InvalidOperationException($"Module '{moduleName}' has an invalid target reference '{target}'. Specify a reference to an OCI artifact."); - - var result = CompilationHelper.Compile(moduleSource); - if (result.Template is null) - { - throw new InvalidOperationException($"Module {moduleName} failed to produce a template."); + await RegistryHelper.PublishModuleToRegistry(clientFactory, moduleName, publishInfo.Metadata.Target, publishInfo.ModuleSource, publishSource, null); } - - var features = featureProviderFactory.GetFeatureProvider(result.BicepFile.FileUri); - BinaryData? sourcesStream = publishSource ? BinaryData.FromStream(SourceArchive.PackSourcesIntoStream(result.Compilation.SourceFileGrouping, features.CacheRootDirectory)) : null; - await dispatcher.PublishModule(targetReference, BinaryData.FromString(result.Template.ToString()), sourcesStream, documentationUri); - } - - public static async Task PublishProviderToRegistryAsync(IDependencyHelper services, string pathToIndexJson, string target) - { - var dispatcher = services.Construct(); - var fileSystem = services.Construct(); - - var targetReference = dispatcher.TryGetArtifactReference(ArtifactType.Provider, target, PathHelper.FilePathToFileUrl(pathToIndexJson)).IsSuccess(out var @ref) ? @ref - : throw new InvalidOperationException($"Invalid target reference '{target}'. Specify a reference to an OCI artifact."); - - var tgzStream = await TypesV1Archive.GenerateProviderTarStream(fileSystem, pathToIndexJson); - - await dispatcher.PublishProvider(targetReference, tgzStream); } private static Uri RandomFileUri() => PathHelper.FilePathToFileUrl(Path.GetTempFileName()); - - public static async Task PublishAzProvider(IDependencyHelper services, string pathToIndexJson) - { - var version = BicepTestConstants.BuiltinAzProviderVersion; - var repository = "bicep/providers/az"; - await PublishProviderToRegistryAsync(services, pathToIndexJson, $"br:{LanguageConstants.BicepPublicMcrRegistry}/{repository}:{version}"); - } - - public static IContainerRegistryClientFactory CreateOciClientForAzProvider() - => CreateMockRegistryClients((LanguageConstants.BicepPublicMcrRegistry, $"bicep/providers/az")).factoryMock; } } diff --git a/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs b/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs new file mode 100644 index 00000000000..524e48661a5 --- /dev/null +++ b/src/Bicep.Core.UnitTests/Utils/RegistryHelper.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using System.IO.Abstractions; +using Bicep.Core.FileSystem; +using Bicep.Core.Registry; +using Bicep.Core.Registry.Providers; +using Bicep.Core.SourceCode; +using Bicep.Core.UnitTests.Features; +using Bicep.Core.UnitTests.Registry; +using Microsoft.Extensions.DependencyInjection; + +namespace Bicep.Core.UnitTests.Utils; + +public static class RegistryHelper +{ + public static IContainerRegistryClientFactory CreateMockRegistryClient(string registry, string repository) + { + return new TestContainerRegistryClientFactoryBuilder() + .RegisterMockRepositoryBlobClient(registry, repository) + .Build().clientFactory; + } + + public static (IContainerRegistryClientFactory factoryMock, ImmutableDictionary<(Uri, string), MockRegistryBlobClient> blobClientMocks) CreateMockRegistryClients(params (string, string)[] clients) + { + var containerRegistryFactoryBuilder = new TestContainerRegistryClientFactoryBuilder(); + + foreach (var (registryHost, repository) in clients) + { + containerRegistryFactoryBuilder.RegisterMockRepositoryBlobClient(registryHost, repository); + + } + + return containerRegistryFactoryBuilder.Build(); + } + + public static async Task PublishModuleToRegistry(IContainerRegistryClientFactory clientFactory, string moduleName, string target, string moduleSource, bool publishSource, string? documentationUri = null) + { + var featureProviderFactory = BicepTestConstants.CreateFeatureProviderFactory(new FeatureProviderOverrides(PublishSourceEnabled: publishSource)); + var dispatcher = ServiceBuilder.Create(s => s.WithDisabledAnalyzersConfiguration() + .AddSingleton(clientFactory) + .AddSingleton(BicepTestConstants.TemplateSpecRepositoryFactory) + .AddSingleton(featureProviderFactory) + ).Construct(); + + var targetReference = dispatcher.TryGetArtifactReference(ArtifactType.Module, target, RandomFileUri()).IsSuccess(out var @ref) ? @ref + : throw new InvalidOperationException($"Module '{moduleName}' has an invalid target reference '{target}'. Specify a reference to an OCI artifact."); + + var result = CompilationHelper.Compile(moduleSource); + if (result.Template is null) + { + throw new InvalidOperationException($"Module {moduleName} failed to produce a template."); + } + + var features = featureProviderFactory.GetFeatureProvider(result.BicepFile.FileUri); + BinaryData? sourcesStream = publishSource ? BinaryData.FromStream(SourceArchive.PackSourcesIntoStream(result.Compilation.SourceFileGrouping, features.CacheRootDirectory)) : null; + await dispatcher.PublishModule(targetReference, BinaryData.FromString(result.Template.ToString()), sourcesStream, documentationUri); + } + + public static async Task PublishProviderToRegistryAsync(IDependencyHelper services, string pathToIndexJson, string target) + { + var dispatcher = services.Construct(); + var fileSystem = services.Construct(); + + var targetReference = dispatcher.TryGetArtifactReference(ArtifactType.Provider, target, PathHelper.FilePathToFileUrl(pathToIndexJson)).IsSuccess(out var @ref) ? @ref + : throw new InvalidOperationException($"Invalid target reference '{target}'. Specify a reference to an OCI artifact."); + + var tgzStream = await TypesV1Archive.GenerateProviderTarStream(fileSystem, pathToIndexJson); + + await dispatcher.PublishProvider(targetReference, tgzStream); + } + + private static Uri RandomFileUri() => PathHelper.FilePathToFileUrl(Path.GetTempFileName()); + + public static async Task PublishAzProvider(IDependencyHelper services, string pathToIndexJson) + { + var version = BicepTestConstants.BuiltinAzProviderVersion; + var repository = "bicep/providers/az"; + await PublishProviderToRegistryAsync(services, pathToIndexJson, $"br:{LanguageConstants.BicepPublicMcrRegistry}/{repository}:{version}"); + } + + public static IContainerRegistryClientFactory CreateOciClientForAzProvider() + => CreateMockRegistryClients((LanguageConstants.BicepPublicMcrRegistry, $"bicep/providers/az")).factoryMock; +} \ No newline at end of file diff --git a/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs b/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs index b5561e469eb..dae71d9377a 100644 --- a/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs +++ b/src/Bicep.Core.UnitTests/Utils/TestContainerRegistryClientFactoryBuilder.cs @@ -14,9 +14,11 @@ public class TestContainerRegistryClientFactoryBuilder { private readonly ImmutableDictionary<(Uri registryUri, string repository), MockRegistryBlobClient>.Builder clientsBuilder = ImmutableDictionary.CreateBuilder<(Uri registryUri, string repository), MockRegistryBlobClient>(); - public void RegisterMockRepositoryBlobClient(string registryHost, string repository) + public TestContainerRegistryClientFactoryBuilder RegisterMockRepositoryBlobClient(string registryHost, string repository) { clientsBuilder.TryAdd((new Uri($"https://{registryHost}"), repository), new MockRegistryBlobClient()); + + return this; } public void RegisterMockRepositoryBlobClient(string registryHost, string repository, MockRegistryBlobClient client) diff --git a/src/Bicep.Core/Workspaces/SourceFileGrouping.cs b/src/Bicep.Core/Workspaces/SourceFileGrouping.cs index ba792483c7d..260d5fa5356 100644 --- a/src/Bicep.Core/Workspaces/SourceFileGrouping.cs +++ b/src/Bicep.Core/Workspaces/SourceFileGrouping.cs @@ -42,13 +42,13 @@ public SourceFileGrouping(IFileResolver fileResolver, public ImmutableDictionary> SourceFileParentLookup { get; } - public IEnumerable GetArtifactsToRestore() + public IEnumerable GetArtifactsToRestore(bool force = false) { foreach (var (sourceFile, artifactResults) in FileUriResultByArtifactReference) { foreach (var (syntax, result) in artifactResults) { - if (!result.IsSuccess(out _, out var failure) && failure.RequiresRestore) + if (force || !result.IsSuccess(out _, out var failure) && failure.RequiresRestore) { yield return new(syntax, sourceFile); } diff --git a/src/Bicep.LangServer.IntegrationTests/LangServerScenarioTests.cs b/src/Bicep.LangServer.IntegrationTests/LangServerScenarioTests.cs index 67d816c6451..95c939194db 100644 --- a/src/Bicep.LangServer.IntegrationTests/LangServerScenarioTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/LangServerScenarioTests.cs @@ -2,9 +2,13 @@ // Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; +using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.UnitTests.Utils; using Bicep.LangServer.IntegrationTests.Assertions; +using Bicep.LanguageServer.Registry; using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -51,4 +55,50 @@ public async Task Test_Issue1931() param foo: string ```"); } + + [TestMethod] + public async Task Test_Issue13254() + { + var clientFactory = RegistryHelper.CreateMockRegistryClient("mockregistry.io", "test/foo"); + async Task publish(string source) + => await RegistryHelper.PublishModuleToRegistry( + clientFactory, + "modulename", + "br:mockregistry.io/test/foo:1.1", + source, + publishSource: false); + + var cacheRootPath = FileHelper.GetUniqueTestOutputPath(TestContext); + var helper = await MultiFileLanguageServerHelper.StartLanguageServer( + TestContext, + services => services + .WithFeatureOverrides(new(CacheRootDirectory: cacheRootPath)) + .WithContainerRegistryClientFactory(clientFactory) + .AddSingleton()); + + await publish("param foo bool"); + + var paramsFileUri = new Uri("file:///main.bicepparam"); + + var diags = await helper.OpenFileOnceAsync(TestContext, """ +using 'br:mockregistry.io/test/foo:1.1' + +param foo = 'abc' +""", paramsFileUri); + diags = await helper.WaitForDiagnostics(paramsFileUri); + diags.Diagnostics.Should().ContainSingle(x => x.Message.Contains("Expected a value of type \"bool\" but the provided value is of type \"'abc'\".")); + + await publish("param foo string"); + + await helper.Client.Workspace.ExecuteCommand(new Command + { + Name = "forceModulesRestore", + Arguments = [ + paramsFileUri.LocalPath, + ] + }); + + diags = await helper.WaitForDiagnostics(paramsFileUri); + diags.Diagnostics.Should().BeEmpty(); + } } diff --git a/src/Bicep.LangServer/BicepCompilationManager.cs b/src/Bicep.LangServer/BicepCompilationManager.cs index f0d21017eae..a9f0c7f0b7f 100644 --- a/src/Bicep.LangServer/BicepCompilationManager.cs +++ b/src/Bicep.LangServer/BicepCompilationManager.cs @@ -184,6 +184,13 @@ public void CloseCompilation(DocumentUri documentUri) CloseCompilationInternal(documentUri, 0, Enumerable.Empty()); } + public void RefreshChangedFiles(IEnumerable files) + => HandleFileChanges(files.Select(uri => new FileEvent + { + Uri = uri, + Type = FileChangeType.Changed, + })); + public void HandleFileChanges(IEnumerable fileEvents) { var modifiedSourceFiles = new HashSet(); diff --git a/src/Bicep.LangServer/CompilationManager/ICompilationManager.cs b/src/Bicep.LangServer/CompilationManager/ICompilationManager.cs index 17eed03d202..fd089edb1ca 100644 --- a/src/Bicep.LangServer/CompilationManager/ICompilationManager.cs +++ b/src/Bicep.LangServer/CompilationManager/ICompilationManager.cs @@ -8,6 +8,8 @@ namespace Bicep.LanguageServer.CompilationManager { public interface ICompilationManager { + void RefreshChangedFiles(IEnumerable files); + void HandleFileChanges(IEnumerable fileEvents); void RefreshCompilation(DocumentUri uri, bool forceReloadAuxiliaryFiles = false); diff --git a/src/Bicep.LangServer/Handlers/BicepForceModulesRestoreCommandHandler.cs b/src/Bicep.LangServer/Handlers/BicepForceModulesRestoreCommandHandler.cs index 4499542e02f..715615cb790 100644 --- a/src/Bicep.LangServer/Handlers/BicepForceModulesRestoreCommandHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepForceModulesRestoreCommandHandler.cs @@ -2,13 +2,16 @@ // Licensed under the MIT License. using System.Text; +using Bicep.Core.Extensions; using Bicep.Core.Features; using Bicep.Core.FileSystem; using Bicep.Core.Registry; using Bicep.Core.Syntax; using Bicep.Core.Workspaces; +using Bicep.LanguageServer.CompilationManager; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; namespace Bicep.LanguageServer.Handlers @@ -19,6 +22,7 @@ public class BicepForceModulesRestoreCommandHandler : ExecuteTypedResponseComman { private readonly IFileResolver fileResolver; private readonly IModuleDispatcher moduleDispatcher; + private readonly ICompilationManager compilationManager; private readonly IWorkspace workspace; private readonly IFeatureProviderFactory featureProviderFactory; @@ -26,12 +30,14 @@ public BicepForceModulesRestoreCommandHandler( ISerializer serializer, IFileResolver fileResolver, IModuleDispatcher moduleDispatcher, + ICompilationManager compilationManager, IWorkspace workspace, IFeatureProviderFactory featureProviderFactory) : base(LangServerConstants.ForceModulesRestoreCommand, serializer) { this.fileResolver = fileResolver; this.moduleDispatcher = moduleDispatcher; + this.compilationManager = compilationManager; this.workspace = workspace; this.featureProviderFactory = featureProviderFactory; } @@ -61,8 +67,13 @@ private async Task ForceModulesRestoreAndGenerateOutputMessage(DocumentU featureProviderFactory); // Ignore modules to restore logic, include all modules to be restored - var artifactsToRestore = sourceFileGrouping.FileUriResultByArtifactReference - .SelectMany(kvp => kvp.Value.Keys.Where(x => x is ModuleDeclarationSyntax or UsingDeclarationSyntax).Select(mds => new ArtifactResolutionInfo(mds, kvp.Key))); + var artifactsToRestore = sourceFileGrouping.GetArtifactsToRestore(force: true); + + var artifactUris = sourceFileGrouping + .FileUriResultByArtifactReference.SelectMany(x => x.Value) + .Select(x => x.Value.TryUnwrap()) + .WhereNotNull() + .Distinct(); // RestoreModules() does a distinct but we'll do it also to prevent duplicates in outputs and logging var artifactReferencesToRestore = this.moduleDispatcher.GetValidModuleReferences(artifactsToRestore) @@ -85,9 +96,8 @@ private async Task ForceModulesRestoreAndGenerateOutputMessage(DocumentU sbRestoreSummary.Append($"{Environment.NewLine} * {module.FullyQualifiedReference}: {restoreStatus}"); } - // Have to actually update compilations to pick up new modules' contents - workspace.UpsertSourceFiles(sourceFileGrouping.SourceFiles); - + // refresh all compilations with a reference to this file or cached artifacts + compilationManager.RefreshChangedFiles(artifactUris.Concat(documentUri.ToUriEncoded())); return $"Restore (force) summary: {sbRestoreSummary}"; } }