diff --git a/src/Bicep.Core.Samples/Files/Modules_CRLF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/Modules_CRLF/main.diagnostics.bicep index a47a0ed1ec5..811647a44dc 100644 --- a/src/Bicep.Core.Samples/Files/Modules_CRLF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/Modules_CRLF/main.diagnostics.bicep @@ -120,6 +120,7 @@ module moduleWithCalculatedName './child/optionalParams.bicep'= { resource resWithCalculatedNameDependencies 'Mock.Rp/mockResource@2020-01-01' = { //@[43:076) [BCP081 (Warning)] Resource type "Mock.Rp/mockResource@2020-01-01" does not have types available. (CodeDescription: none) |'Mock.Rp/mockResource@2020-01-01'| name: '${optionalWithAllParamsAndManualDependency.name}${deployTimeSuffix}' +//@[08:077) [use-stable-resource-identifiers (Warning)] Resource identifiers should be reproducible outside of their initial deployment context. Resource resWithCalculatedNameDependencies's 'name' identifier is potentially nondeterministic due to its use of the 'newGuid' function (resWithCalculatedNameDependencies.name -> deployTimeSuffix (default value) -> newGuid()). (CodeDescription: bicep core(https://aka.ms/bicep/linter/use-stable-resource-identifiers)) |'${optionalWithAllParamsAndManualDependency.name}${deployTimeSuffix}'| properties: { modADep: moduleWithCalculatedName.outputs.outputObj } diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseStableResourceIdentifiersRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseStableResourceIdentifiersRuleTests.cs new file mode 100644 index 00000000000..e075874f2e2 --- /dev/null +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseStableResourceIdentifiersRuleTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Linq; +using Bicep.Core.Analyzers.Linter.Rules; +using Bicep.Core.UnitTests.Assertions; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Bicep.Core.UnitTests.Diagnostics.LinterRuleTests +{ + [TestClass] + public class UseStableResourceIdentifiersRuleTests : LinterRuleTestsBase + { + private void CompileAndTest(string text, params string[] expectedMessages) + { + AssertLinterRuleDiagnostics(UseStableResourceIdentifiersRule.Code, text, diags => + { + if (expectedMessages.Any()) + { + diags.Where(e => e.Code == UseStableResourceIdentifiersRule.Code).Select(e => e.Message).Should().Contain(expectedMessages); + } + else + { + diags.Where(e => e.Code == UseStableResourceIdentifiersRule.Code).Count().Should().Be(0); + } + }); + } + + [DataRow(@" + param location string = resourceGroup().location + + resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: 'literalName' + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + }" + )] + [DataRow(@" + param location string = resourceGroup().location + param snap string + + var crackle = 'crackle' + var pop = '${snap}${crackle}' + + resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: pop + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + }" + )] + [DataRow(@" + param location string = resourceGroup().location + param snap string = newGuid() + + var crackle = snap + var pop = '${snap}${crackle}' + + resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: pop + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + }", + "Resource identifiers should be reproducible outside of their initial deployment context. Resource storage's 'name' identifier is potentially nondeterministic due to its use of the 'newGuid' function (storage.name -> pop -> snap (default value) -> newGuid()).", + "Resource identifiers should be reproducible outside of their initial deployment context. Resource storage's 'name' identifier is potentially nondeterministic due to its use of the 'newGuid' function (storage.name -> pop -> crackle -> snap (default value) -> newGuid())." + )] + [DataRow(@" + param location string = resourceGroup().location + param snap string = utcNow('F') + + var crackle = snap + var pop = '${snap}${crackle}' + + resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: pop + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + }", + "Resource identifiers should be reproducible outside of their initial deployment context. Resource storage's 'name' identifier is potentially nondeterministic due to its use of the 'utcNow' function (storage.name -> pop -> snap (default value) -> utcNow('F')).", + "Resource identifiers should be reproducible outside of their initial deployment context. Resource storage's 'name' identifier is potentially nondeterministic due to its use of the 'utcNow' function (storage.name -> pop -> crackle -> snap (default value) -> utcNow('F'))." + )] + [DataTestMethod] + public void TestRule(string text, params string[] expectedMessages) + { + CompileAndTest(text, expectedMessages); + } + } +} diff --git a/src/Bicep.Core/Analyzers/Linter/Rules/UseStableResourceIdentifiersRule.cs b/src/Bicep.Core/Analyzers/Linter/Rules/UseStableResourceIdentifiersRule.cs new file mode 100644 index 00000000000..ac45586bc43 --- /dev/null +++ b/src/Bicep.Core/Analyzers/Linter/Rules/UseStableResourceIdentifiersRule.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Diagnostics; +using Bicep.Core.Navigation; +using Bicep.Core.Semantics; +using Bicep.Core.Syntax; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bicep.Core.Analyzers.Linter.Rules +{ + public sealed class UseStableResourceIdentifiersRule : LinterRuleBase + { + public new const string Code = "use-stable-resource-identifiers"; + + public UseStableResourceIdentifiersRule() : base( + code: Code, + description: CoreResources.UseStableResourceIdentifiersMessage, + docUri: new Uri($"https://aka.ms/bicep/linter/{Code}")) + { } + + public override IEnumerable AnalyzeInternal(SemanticModel model) + { + foreach (var resource in model.DeclaredResources) + { + foreach (var identifier in resource.Type.UniqueIdentifierProperties) + { + if (resource.Symbol.TryGetBodyPropertyValue(identifier) is { } identifierSyntax) + { + var visitor = new Visitor(model); + identifierSyntax.Accept(visitor); + foreach (var (path, functionName) in visitor.PathsToNonDeterministicFunctionsUsed) + { + yield return CreateDiagnosticForSpan(identifierSyntax.Span, resource.Symbol.Name, identifier, functionName, $"{resource.Symbol.Name}.{identifier} -> {path}"); + } + } + } + } + } + + public override string FormatMessage(params object[] values) + => string.Format(CoreResources.UseStableResourceIdentifiersMessageFormat, values); + + private class Visitor : SyntaxVisitor + { + private static IReadOnlySet NonDeterministicFunctionNames = new HashSet + { + "newGuid", + "utcNow", + }; + private readonly SemanticModel model; + private readonly Dictionary pathsToNonDeterministicFunctionsUsed = new(); + private readonly LinkedList pathSegments = new(); + + internal Visitor(SemanticModel model) + { + this.model = model; + } + + internal IEnumerable> PathsToNonDeterministicFunctionsUsed => pathsToNonDeterministicFunctionsUsed; + + public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax) + { + if (NonDeterministicFunctionNames.Contains(syntax.Name.IdentifierName)) + { + pathsToNonDeterministicFunctionsUsed.Add(FormatPath(syntax.ToText()), syntax.Name.IdentifierName); + } + base.VisitFunctionCallSyntax(syntax); + } + + public override void VisitVariableAccessSyntax(VariableAccessSyntax syntax) + { + switch (model.GetSymbolInfo(syntax)) + { + case ParameterSymbol @parameter: + if (@parameter.DeclaringParameter.Modifier is ParameterDefaultValueSyntax defaultValueSyntax) + { + pathSegments.AddLast(@parameter); + defaultValueSyntax.Accept(this); + pathSegments.RemoveLast(); + } + break; + case VariableSymbol @variable: + // Variable cycles are reported on elsewhere. As far as this visitor is concerned, a cycle does not introduce nondeterminism. + if (pathSegments.Contains(@variable)) + { + return; + } + + pathSegments.AddLast(@variable); + @variable.DeclaringVariable.Value.Accept(this); + pathSegments.RemoveLast(); + break; + } + + base.VisitVariableAccessSyntax(syntax); + } + + private string FormatPath(string functionCall) + { + var path = new StringBuilder(); + foreach (var segment in pathSegments) + { + if (segment is ParameterSymbol @parameter) + { + path.Append(@parameter.Name); + path.Append(" (default value) -> "); + } + else if (segment is VariableSymbol @variable) + { + path.Append(@variable.Name); + path.Append(" -> "); + } + } + + path.Append(functionCall); + + return path.ToString(); + } + } + } +} diff --git a/src/Bicep.Core/CoreResources.Designer.cs b/src/Bicep.Core/CoreResources.Designer.cs index b4c888f0a13..59523ec487b 100644 --- a/src/Bicep.Core/CoreResources.Designer.cs +++ b/src/Bicep.Core/CoreResources.Designer.cs @@ -10,8 +10,8 @@ namespace Bicep.Core { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace Bicep.Core { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class CoreResources { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal CoreResources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ internal CoreResources() { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,7 +59,7 @@ internal CoreResources() { resourceCulture = value; } } - + /// /// Looks up a localized string similar to Property 'adminUserName' should not use a literal value. Use a param instead.. /// @@ -68,7 +68,7 @@ internal static string AdminUsernameShouldNotBeLiteralRuleDescription { return ResourceManager.GetString("AdminUsernameShouldNotBeLiteralRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Custom bicepconfig.json file found ({0}).. /// @@ -77,7 +77,7 @@ internal static string BicepConfigCustomSettingsFoundFormatMessage { return ResourceManager.GetString("BicepConfigCustomSettingsFoundFormatMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to No bicepconfig.json found for configuration override.. /// @@ -86,7 +86,7 @@ internal static string BicepConfigNoCustomSettingsMessage { return ResourceManager.GetString("BicepConfigNoCustomSettingsMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Environment URLs should not be hardcoded. Use the environment() function to ensure compatibility across clouds.. /// @@ -95,7 +95,7 @@ internal static string EnvironmentUrlHardcodedRuleDescription { return ResourceManager.GetString("EnvironmentUrlHardcodedRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Use string interpolation: {0}.. /// @@ -104,7 +104,7 @@ internal static string InterpolateNotConcatFixTitle { return ResourceManager.GetString("InterpolateNotConcatFixTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to Use string interpolation instead of the concat function.. /// @@ -113,7 +113,7 @@ internal static string InterpolateNotConcatRuleDescription { return ResourceManager.GetString("InterpolateNotConcatRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Linter is disabled in settings file located at {0} . /// @@ -122,7 +122,7 @@ internal static string LinterDisabledFormatMessage { return ResourceManager.GetString("LinterDisabledFormatMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Analyzer '{0}' encountered an unexpected exception. {1}. /// @@ -131,7 +131,7 @@ internal static string LinterRuleExceptionMessageFormat { return ResourceManager.GetString("LinterRuleExceptionMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to Resource location should be specified by a parameter without a default value or one that defaults to 'global' or resourceGroup().location.. /// @@ -140,7 +140,7 @@ internal static string LocationSetByParameterRuleDescription { return ResourceManager.GetString("LocationSetByParameterRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Maximum number of outputs used.. /// @@ -149,7 +149,7 @@ internal static string MaxNumberOutputsRuleDescription { return ResourceManager.GetString("MaxNumberOutputsRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Too many outputs. Number of outputs is limited to {0}.. /// @@ -158,7 +158,7 @@ internal static string MaxNumberOutputsRuleMessageFormat { return ResourceManager.GetString("MaxNumberOutputsRuleMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to Maximum number of parameters used.. /// @@ -167,7 +167,7 @@ internal static string MaxNumberParametersRuleDescription { return ResourceManager.GetString("MaxNumberParametersRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Too many parameters. Number of parameters is limited to {0}.. /// @@ -176,7 +176,7 @@ internal static string MaxNumberParametersRuleMessageFormat { return ResourceManager.GetString("MaxNumberParametersRuleMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to Maximum number of resources used.. /// @@ -185,7 +185,7 @@ internal static string MaxNumberResourcesRuleDescription { return ResourceManager.GetString("MaxNumberResourcesRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Too many resources. Number of resources is limited to {0}.. /// @@ -194,7 +194,7 @@ internal static string MaxNumberResourcesRuleMessageFormat { return ResourceManager.GetString("MaxNumberResourcesRuleMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to Maximum number of variables used.. /// @@ -203,7 +203,7 @@ internal static string MaxNumberVariablesRuleDescription { return ResourceManager.GetString("MaxNumberVariablesRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Too many variables. Number of variables is limited to {0}.. /// @@ -212,7 +212,7 @@ internal static string MaxNumberVariablesRuleMessageFormat { return ResourceManager.GetString("MaxNumberVariablesRuleMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to A resource location should not use a hard-coded string or variable value. Change variable '{0}' into a parameter instead.. /// @@ -221,7 +221,7 @@ internal static string NoHardcodedLocation_ErrorChangeVarToParam { return ResourceManager.GetString("NoHardcodedLocation_ErrorChangeVarToParam", resourceCulture); } } - + /// /// Looks up a localized string similar to Parameter '{0}' may be used as a resource location in the module and should not be assigned a hard-coded string or variable value.. /// @@ -230,7 +230,7 @@ internal static string NoHardcodedLocation_ErrorForModuleParam { return ResourceManager.GetString("NoHardcodedLocation_ErrorForModuleParam", resourceCulture); } } - + /// /// Looks up a localized string similar to A resource location should not use a hard-coded string or variable value.. /// @@ -239,7 +239,7 @@ internal static string NoHardcodedLocation_ErrorForResourceLocation { return ResourceManager.GetString("NoHardcodedLocation_ErrorForResourceLocation", resourceCulture); } } - + /// /// Looks up a localized string similar to Please use a parameter value, an expression, or the string '{0}'. Found: '{1}'. /// @@ -248,7 +248,7 @@ internal static string NoHardcodedLocation_ErrorSolution { return ResourceManager.GetString("NoHardcodedLocation_ErrorSolution", resourceCulture); } } - + /// /// Looks up a localized string similar to Change variable '{0}' into a parameter instead. /// @@ -257,7 +257,7 @@ internal static string NoHardcodedLocation_FixChangeVarToParam { return ResourceManager.GetString("NoHardcodedLocation_FixChangeVarToParam", resourceCulture); } } - + /// /// Looks up a localized string similar to Create new parameter '{0}' with default value {1}. /// @@ -266,7 +266,7 @@ internal static string NoHardcodedLocation_FixNewParam { return ResourceManager.GetString("NoHardcodedLocation_FixNewParam", resourceCulture); } } - + /// /// Looks up a localized string similar to Parameter '{0}' of module '{1}' isn't assigned an explicit value, and its default value may not give the intended behavior for a location-related parameter. You should assign an explicit value to the parameter.. /// @@ -275,7 +275,7 @@ internal static string NoHardcodedLocation_ModuleLocationNeedsExplicitValue { return ResourceManager.GetString("NoHardcodedLocation_ModuleLocationNeedsExplicitValue", resourceCulture); } } - + /// /// Looks up a localized string similar to A resource location should be either an expression or the string '{0}'. Found '{1}'. /// @@ -284,7 +284,7 @@ internal static string NoHardcodedLocation_ResourceLocationShouldBeExpressionOrG return ResourceManager.GetString("NoHardcodedLocation_ResourceLocationShouldBeExpressionOrGlobal", resourceCulture); } } - + /// /// Looks up a localized string similar to A resource's location should not use a hard-coded string or variable value. It should use a parameter, an expression, or the string 'global'.. /// @@ -293,7 +293,7 @@ internal static string NoHardcodedLocationRuleDescription { return ResourceManager.GetString("NoHardcodedLocationRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Functions resourceGroup().location and deployment().location should only be used as the default value of a parameter.. /// @@ -302,7 +302,7 @@ internal static string NoLocExprOutsideParamsRuleDescription { return ResourceManager.GetString("NoLocExprOutsideParamsRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Use a parameter here instead of '{0}'. 'resourceGroup().location' and 'deployment().location' should only be used as a default value for parameters.. /// @@ -311,7 +311,7 @@ internal static string NoLocExprOutsideParamsRuleError { return ResourceManager.GetString("NoLocExprOutsideParamsRuleError", resourceCulture); } } - + /// /// Looks up a localized string similar to No unnecessary dependsOn.. /// @@ -320,7 +320,7 @@ internal static string NoUnnecessaryDependsOnRuleDescription { return ResourceManager.GetString("NoUnnecessaryDependsOnRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Remove unnecessary dependsOn entry '{0}'.. /// @@ -329,7 +329,7 @@ internal static string NoUnnecessaryDependsOnRuleMessage { return ResourceManager.GetString("NoUnnecessaryDependsOnRuleMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to function '{0}'. /// @@ -338,7 +338,7 @@ internal static string OutputsShouldNotContainSecretsFunction { return ResourceManager.GetString("OutputsShouldNotContainSecretsFunction", resourceCulture); } } - + /// /// Looks up a localized string similar to {0} Found possible secret: {1}. /// @@ -347,7 +347,7 @@ internal static string OutputsShouldNotContainSecretsMessageFormat { return ResourceManager.GetString("OutputsShouldNotContainSecretsMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to output name '{0}' suggests a secret. /// @@ -356,7 +356,7 @@ internal static string OutputsShouldNotContainSecretsOutputName { return ResourceManager.GetString("OutputsShouldNotContainSecretsOutputName", resourceCulture); } } - + /// /// Looks up a localized string similar to Outputs should not contain secrets.. /// @@ -365,7 +365,7 @@ internal static string OutputsShouldNotContainSecretsRuleDescription { return ResourceManager.GetString("OutputsShouldNotContainSecretsRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to secure parameter '{0}'. /// @@ -374,7 +374,7 @@ internal static string OutputsShouldNotContainSecretsSecureParam { return ResourceManager.GetString("OutputsShouldNotContainSecretsSecureParam", resourceCulture); } } - + /// /// Looks up a localized string similar to All parameters must be used.. /// @@ -383,7 +383,7 @@ internal static string ParameterMustBeUsedRuleDescription { return ResourceManager.GetString("ParameterMustBeUsedRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Parameter "{0}" is declared but never used.. /// @@ -392,7 +392,7 @@ internal static string ParameterMustBeUsedRuleMessageFormat { return ResourceManager.GetString("ParameterMustBeUsedRuleMessageFormat", resourceCulture); } } - + /// /// Looks up a localized string similar to function '{0}'. /// @@ -401,7 +401,7 @@ internal static string PossibleSecretMessageFunction { return ResourceManager.GetString("PossibleSecretMessageFunction", resourceCulture); } } - + /// /// Looks up a localized string similar to secure parameter '{0}'. /// @@ -410,7 +410,7 @@ internal static string PossibleSecretMessageSecureParam { return ResourceManager.GetString("PossibleSecretMessageSecureParam", resourceCulture); } } - + /// /// Looks up a localized string similar to Remove quotes from property name '{0}'.. /// @@ -419,7 +419,7 @@ internal static string PreferUnquotedPropertyNamesDeclarationFixTitle { return ResourceManager.GetString("PreferUnquotedPropertyNamesDeclarationFixTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to Replace array syntax with '{0}'.. /// @@ -428,7 +428,7 @@ internal static string PreferUnquotedPropertyNamesDereferenceFixTitle { return ResourceManager.GetString("PreferUnquotedPropertyNamesDereferenceFixTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to Property names that are valid identifiers should be declared without quotation marks and accessed using dot notation.. /// @@ -437,7 +437,7 @@ internal static string PreferUnquotedPropertyNamesRuleDescription { return ResourceManager.GetString("PreferUnquotedPropertyNamesRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Use protectedSettings for commandToExecute secrets. /// @@ -446,7 +446,7 @@ internal static string ProtectCommandToExecuteSecretsRuleDescription { return ResourceManager.GetString("ProtectCommandToExecuteSecretsRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Use protectedSettings for commandToExecute secrets. Found possible secret: {0}. /// @@ -455,7 +455,7 @@ internal static string ProtectCommandToExecuteSecretsRuleMessage { return ResourceManager.GetString("ProtectCommandToExecuteSecretsRuleMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Remove insecure default value.. /// @@ -464,7 +464,7 @@ internal static string SecureParameterDefaultFixTitle { return ResourceManager.GetString("SecureParameterDefaultFixTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to Secure parameters should not have hardcoded defaults (except for empty or newGuid()).. /// @@ -473,7 +473,7 @@ internal static string SecureParameterDefaultRuleDescription { return ResourceManager.GetString("SecureParameterDefaultRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Remove unnecessary string interpolation.. /// @@ -482,7 +482,7 @@ internal static string SimplifyInterpolationFixTitle { return ResourceManager.GetString("SimplifyInterpolationFixTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to Remove unnecessary string interpolation.. /// @@ -491,7 +491,7 @@ internal static string SimplifyInterpolationRuleDescription { return ResourceManager.GetString("SimplifyInterpolationRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to All variables must be used.. /// @@ -500,7 +500,7 @@ internal static string UnusedVariableRuleDescription { return ResourceManager.GetString("UnusedVariableRuleDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Variable "{0}" is declared but never used.. /// @@ -509,7 +509,25 @@ internal static string UnusedVariableRuleMessageFormat { return ResourceManager.GetString("UnusedVariableRuleMessageFormat", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Resource identifiers should be reproducible outside of their initial deployment context. . + /// + internal static string UseStableResourceIdentifiersMessage { + get { + return ResourceManager.GetString("UseStableResourceIdentifiersMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Resource identifiers should be reproducible outside of their initial deployment context. Resource {0}'s '{1}' identifier is potentially nondeterministic due to its use of the '{2}' function ({3}).. + /// + internal static string UseStableResourceIdentifiersMessageFormat { + get { + return ResourceManager.GetString("UseStableResourceIdentifiersMessageFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Virtual machines shouldn't use preview images.. /// @@ -518,7 +536,7 @@ internal static string UseStableVMImage { return ResourceManager.GetString("UseStableVMImage", resourceCulture); } } - + /// /// Looks up a localized string similar to Virtual machines shouldn't use preview images. Use stable version in imageReference property "{0}".. /// diff --git a/src/Bicep.Core/CoreResources.resx b/src/Bicep.Core/CoreResources.resx index 924306fd28b..9062aef06a5 100644 --- a/src/Bicep.Core/CoreResources.resx +++ b/src/Bicep.Core/CoreResources.resx @@ -1,17 +1,17 @@ - @@ -297,4 +297,11 @@ Property names that are valid identifiers should be declared without quotation marks and accessed using dot notation. - + + Resource identifiers should be reproducible outside of their initial deployment context. + + + Resource identifiers should be reproducible outside of their initial deployment context. Resource {0}'s '{1}' identifier is potentially nondeterministic due to its use of the '{2}' function ({3}). + {0} is the symbolic name of the resource. {1} is the name of the identifier property. {2} is the name of the nondeterministic function used. {3} is the symbol dereference path by which the nondeterministic function call is included. + + \ No newline at end of file diff --git a/src/vscode-bicep/schemas/bicepconfig.schema.json b/src/vscode-bicep/schemas/bicepconfig.schema.json index 5b5ac8ee74d..fd5383f2eea 100644 --- a/src/vscode-bicep/schemas/bicepconfig.schema.json +++ b/src/vscode-bicep/schemas/bicepconfig.schema.json @@ -461,6 +461,16 @@ "$ref": "#/definitions/rule-def-error-level" } ] + }, + "use-stable-resource-identifiers": { + "allOf": [ + { + "description": "Resource identifiers should be reproducible outside of their initial deployment context. See https://aka.ms/bicep/linter/use-stable-resource-identifiers" + }, + { + "$ref": "#/definitions/rule" + } + ] } } }