From aa99e350f81e00422994356e16f1d5b1621d9f96 Mon Sep 17 00:00:00 2001 From: Anthony Martin <38542602+anthony-c-martin@users.noreply.github.com> Date: Wed, 9 Oct 2024 01:59:37 -0400 Subject: [PATCH] Support for `@deprecated()` decorator --- .../DeprecationTests.cs | 228 ++++++++++++++++++ .../Files/baselines/Imports_LF/main.bicep | 2 + .../Imports_LF/main.diagnostics.bicep | 11 +- .../baselines/Imports_LF/main.formatted.bicep | 2 + .../Files/baselines/Imports_LF/main.ir.bicep | 4 +- .../Files/baselines/Imports_LF/main.json | 34 ++- .../baselines/Imports_LF/main.sourcemap.bicep | 34 ++- .../Imports_LF/main.symbolicnames.json | 34 ++- .../baselines/Imports_LF/main.symbols.bicep | 5 + .../baselines/Imports_LF/main.syntax.bicep | 25 +- .../baselines/Imports_LF/main.tokens.bicep | 13 + .../Imports_LF/modules/deprecations.bicep | 19 ++ .../Completions/decorators.json | 21 ++ .../Completions/decoratorsPlusNamespace.json | 21 ++ .../Completions/intParameterDecorators.json | 21 ++ .../intParameterDecoratorsPlusNamespace.json | 21 ++ .../stringParameterDecorators.json | 21 ++ ...tringParameterDecoratorsPlusNamespace.json | 21 ++ .../main.diagnostics.bicep | 2 +- .../Completions/description.json | 21 ++ .../Completions/sysAndDescription.json | 21 ++ .../Modules_CRLF/child/deprecations.bicep | 19 ++ .../Files/baselines/Modules_CRLF/main.bicep | 9 + .../Modules_CRLF/main.diagnostics.bicep | 12 + .../Modules_CRLF/main.formatted.bicep | 9 + .../baselines/Modules_CRLF/main.ir.bicep | 20 +- .../Files/baselines/Modules_CRLF/main.json | 92 ++++++- .../Modules_CRLF/main.sourcemap.bicep | 101 +++++++- .../Modules_CRLF/main.symbolicnames.json | 92 ++++++- .../baselines/Modules_CRLF/main.symbols.bicep | 11 + .../baselines/Modules_CRLF/main.syntax.bicep | 65 ++++- .../baselines/Modules_CRLF/main.tokens.bicep | 42 +++- .../Rules/NoDeprecatedDependenciesRule.cs | 84 +++++++ .../Fixes/DecoratorCodeFixProvider.cs | 3 +- src/Bicep.Core/CoreResources.Designer.cs | 27 +++ src/Bicep.Core/CoreResources.resx | 11 +- .../Diagnostics/DiagnosticBuilder.cs | 12 + .../Emit/EmitLimitationCalculator.cs | 24 ++ src/Bicep.Core/Emit/TemplateWriter.cs | 83 ++++--- src/Bicep.Core/Intermediate/Expression.cs | 57 +++-- src/Bicep.Core/LanguageConstants.cs | 2 + .../Semantics/ArmTemplateSemanticModel.cs | 39 ++- .../Semantics/DeclaredFunctionSymbol.cs | 2 +- src/Bicep.Core/Semantics/DescriptionHelper.cs | 32 --- src/Bicep.Core/Semantics/FactsCache.cs | 107 ++++++++ .../Semantics/Metadata/ExportMetadata.cs | 18 +- .../Semantics/Metadata/OutputMetadata.cs | 2 +- .../Semantics/Metadata/ParameterMetadata.cs | 2 +- .../Semantics/NameBindingVisitor.cs | 5 +- .../Namespaces/SystemNamespaceType.cs | 16 ++ src/Bicep.Core/Semantics/SemanticModel.cs | 23 +- .../Semantics/SemanticModelHelper.cs | 8 + src/Bicep.Core/Semantics/SymbolExtensions.cs | 2 +- src/Bicep.Core/Semantics/SymbolValidator.cs | 5 + .../TypeSystem/DeclaredTypeManager.cs | 44 ++-- src/Bicep.Core/TypeSystem/FunctionFlags.cs | 18 +- .../TypeSystem/TypeAssignmentVisitor.cs | 1 + src/Bicep.Core/TypeSystem/TypeManager.cs | 2 +- .../Completions/BicepCompletionProvider.cs | 4 +- .../Handlers/BicepHoverHandler.cs | 11 +- .../schemas/bicepconfig.schema.json | 10 + 61 files changed, 1528 insertions(+), 179 deletions(-) create mode 100644 src/Bicep.Core.IntegrationTests/DeprecationTests.cs create mode 100644 src/Bicep.Core.Samples/Files/baselines/Imports_LF/modules/deprecations.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/child/deprecations.bicep create mode 100644 src/Bicep.Core/Analyzers/Linter/Rules/NoDeprecatedDependenciesRule.cs create mode 100644 src/Bicep.Core/Semantics/FactsCache.cs diff --git a/src/Bicep.Core.IntegrationTests/DeprecationTests.cs b/src/Bicep.Core.IntegrationTests/DeprecationTests.cs new file mode 100644 index 00000000000..a6bfec83646 --- /dev/null +++ b/src/Bicep.Core.IntegrationTests/DeprecationTests.cs @@ -0,0 +1,228 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Bicep.Core.Diagnostics; +using Bicep.Core.UnitTests; +using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.UnitTests.Utils; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Bicep.Core.IntegrationTests; + +[TestClass] +public class DeprecationTests +{ + [NotNull] public TestContext? TestContext { get; set; } + + [TestMethod] + public void Deprecation_is_represented_in_metadata() + { + var result = CompilationHelper.Compile(""" +@deprecated('deprecated param') +param fooParam string? + +@export() +@deprecated('deprecated var') +var fooVar = '' + +@export() +@deprecated('deprecated func') +func fooFunc() string => ':(' + +@export() +@deprecated('deprecated type') +type fooType = { + bar: string +} + +@deprecated('deprecated output') +output fooOutput string = ':(' +"""); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + result.Template.Should().HaveValueAtPath($"$.parameters.fooParam.metadata.__bicep_deprecated!", "deprecated param"); + result.Template.Should().HaveValueAtPath($"$.metadata.__bicep_exported_variables![?(@.name == 'fooVar')].metadata.__bicep_deprecated!", "deprecated var"); + result.Template.Should().HaveValueAtPath($"$.functions[0].members.fooFunc.metadata.__bicep_deprecated!", "deprecated func"); + result.Template.Should().HaveValueAtPath($"$.definitions.fooType.metadata.__bicep_deprecated!", "deprecated type"); + result.Template.Should().HaveValueAtPath($"$.outputs.fooOutput.metadata.__bicep_deprecated!", "deprecated output"); + } + + [TestMethod] + public void Type_properties_cannot_be_deprecated() + { + // This is a limitation of the current implementation - something we should try and fix longer-term + var result = CompilationHelper.Compile(""" +@export() +type fooType = { + @deprecated('deprecated type property') + bar: string +} +"""); + + result.ExcludingLinterDiagnostics().Should().HaveDiagnostics([ + ("BCP410", DiagnosticLevel.Error, $"""Function "deprecated" cannot be used as a type property decorator."""), + ]); + } + + [TestMethod] + public void Deprecating_unexported_types_vars_and_funcs_is_blocked() + { + var result = CompilationHelper.Compile(""" +@deprecated('deprecated var') +var fooVar = '' + +@deprecated('deprecated func') +func fooFunc() string => ':(' + +@deprecated('deprecated type') +type fooType = { + bar: string +} +"""); + + result.ExcludingLinterDiagnostics().Should().HaveDiagnostics([ + ("BCP411", DiagnosticLevel.Error, $"""This declaration cannot be marked as deprecated, because it has not been exported."""), + ("BCP411", DiagnosticLevel.Error, $"""This declaration cannot be marked as deprecated, because it has not been exported."""), + ("BCP411", DiagnosticLevel.Error, $"""This declaration cannot be marked as deprecated, because it has not been exported."""), + ]); + } + + [TestMethod] + public void Deprecating_required_params_is_blocked() + { + var result = CompilationHelper.Compile(""" +@deprecated('deprecated required') +param required string +"""); + + result.ExcludingLinterDiagnostics().Should().HaveDiagnostics([ + ("BCP412", DiagnosticLevel.Error, $"""Parameters must either be nullable or have a default value to be marked as deprecated."""), + ]); + } + + [TestMethod] + public void Deprecation_reason_is_optional() + { + var result = CompilationHelper.Compile(""" +@deprecated() +param optional string = '' +"""); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + } + + [TestMethod] + public void Importing_deprecated_types_vars_and_funcs_is_flagged() + { + var result = CompilationHelper.Compile(("main.bicep", """ +import { fooVar, fooFunc, fooType } from 'module.bicep' +"""), ("module.bicep", """ +@export() +@deprecated('deprecated var') +var fooVar = '' + +@export() +@deprecated('deprecated func') +func fooFunc() string => ':(' + +@export() +@deprecated('deprecated type') +type fooType = { + bar: string +} +""")); + + result.Should().HaveDiagnostics([ + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooVar' has been marked as deprecated, and should not be used. Reason: 'deprecated var'."""), + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooFunc' has been marked as deprecated, and should not be used. Reason: 'deprecated func'."""), + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooType' has been marked as deprecated, and should not be used. Reason: 'deprecated type'."""), + ]); + } + + [TestMethod] + public void Importing_deprecated_types_vars_and_funcs_is_flagged_from_json() + { + var json = CompilationHelper.Compile(""" +@export() +@deprecated('deprecated var') +var fooVar = '' + +@export() +@deprecated('deprecated func') +func fooFunc() string => ':(' + +@export() +@deprecated('deprecated type') +type fooType = { + bar: string +} +""").Template!.ToString(); + + + var result = CompilationHelper.Compile(("main.bicep", """ +import { fooVar, fooFunc, fooType } from 'module.json' +"""), ("module.json", json)); + + result.Should().HaveDiagnostics([ + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooVar' has been marked as deprecated, and should not be used. Reason: 'deprecated var'."""), + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooFunc' has been marked as deprecated, and should not be used. Reason: 'deprecated func'."""), + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooType' has been marked as deprecated, and should not be used. Reason: 'deprecated type'."""), + ]); + } + + [TestMethod] + public void Setting_params_and_accessing_outputs_is_flagged_if_deprecated() + { + var result = CompilationHelper.Compile(("main.bicep", """ +module mod 'module.bicep' = { + name: 'mod' + params: { + fooParam: '' + } +} + +var test = mod.outputs.fooOutput +"""), ("module.bicep", """ +@deprecated('deprecated param') +param fooParam string? + +@deprecated('deprecated output') +output fooOutput string = ':(' +""")); + + result.ExcludingDiagnostics("no-unused-vars").Should().HaveDiagnostics([ + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooParam' has been marked as deprecated, and should not be used. Reason: 'deprecated param'."""), + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooOutput' has been marked as deprecated, and should not be used. Reason: 'deprecated output'."""), + ]); + } + + [TestMethod] + public void Setting_params_and_accessing_outputs_is_flagged_if_deprecated_from_json() + { + var json = CompilationHelper.Compile(""" +@deprecated('deprecated param') +param fooParam string? + +@deprecated('deprecated output') +output fooOutput string = ':(' +""").Template!.ToString(); + + var result = CompilationHelper.Compile(("main.bicep", """ +module mod 'module.json' = { + name: 'mod' + params: { + fooParam: '' + } +} + +var test = mod.outputs.fooOutput +"""), ("module.json", json)); + + result.ExcludingDiagnostics("no-unused-vars").Should().HaveDiagnostics([ + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooParam' has been marked as deprecated, and should not be used. Reason: 'deprecated param'."""), + ("no-deprecated-dependencies", DiagnosticLevel.Warning, $"""Symbol 'fooOutput' has been marked as deprecated, and should not be used. Reason: 'deprecated output'."""), + ]); + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.bicep index 35bf449caae..63401cc8818 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.bicep @@ -5,6 +5,8 @@ import { refersToCopyVariable } from 'modules/mod.json' +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' + var aliasedFoo = foo var aliasedBar = mod2.foo diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.diagnostics.bicep index a55fe31a95d..e0a8f344a4a 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.diagnostics.bicep @@ -5,15 +5,20 @@ import { refersToCopyVariable } from 'modules/mod.json' +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' +//@[09:16) [no-deprecated-dependencies (Warning)] Symbol 'fooFunc' has been marked as deprecated, and should not be used. Reason: 'deprecated func'. (bicep core linter https://aka.ms/bicep/linter/no-deprecated-dependencies) |fooFunc| +//@[18:24) [no-deprecated-dependencies (Warning)] Symbol 'fooVar' has been marked as deprecated, and should not be used. Reason: 'deprecated var'. (bicep core linter https://aka.ms/bicep/linter/no-deprecated-dependencies) |fooVar| +//@[26:33) [no-deprecated-dependencies (Warning)] Symbol 'fooType' has been marked as deprecated, and should not be used. Reason: 'deprecated type'. (bicep core linter https://aka.ms/bicep/linter/no-deprecated-dependencies) |fooType| + var aliasedFoo = foo -//@[4:14) [no-unused-vars (Warning)] Variable "aliasedFoo" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-vars) |aliasedFoo| +//@[04:14) [no-unused-vars (Warning)] Variable "aliasedFoo" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-vars) |aliasedFoo| var aliasedBar = mod2.foo -//@[4:14) [no-unused-vars (Warning)] Variable "aliasedBar" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-vars) |aliasedBar| +//@[04:14) [no-unused-vars (Warning)] Variable "aliasedBar" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-vars) |aliasedBar| type fizzes = fizz[] param fizzParam mod2.fizz -//@[6:15) [no-unused-params (Warning)] Parameter "fizzParam" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-params) |fizzParam| +//@[06:15) [no-unused-params (Warning)] Parameter "fizzParam" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-params) |fizzParam| output magicWord pop = refersToCopyVariable[3].value output greeting string = greet('friend') diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.formatted.bicep index 5bea918a6db..7fba83325b9 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.formatted.bicep @@ -5,6 +5,8 @@ import { refersToCopyVariable } from 'modules/mod.json' +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' + var aliasedFoo = foo var aliasedBar = mod2.foo diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.ir.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.ir.bicep index 2c79adb5edd..a5668c650e6 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.ir.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.ir.bicep @@ -1,11 +1,13 @@ import {foo, fizz, pop, greet} from 'modules/mod.bicep' -//@[00:407) ProgramExpression +//@[00:478) ProgramExpression import * as mod2 from 'modules/mod2.bicep' import { 'not-a-valid-bicep-identifier' as withInvalidIdentifier refersToCopyVariable } from 'modules/mod.json' +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' + var aliasedFoo = foo //@[00:020) ├─DeclaredVariableExpression { Name = aliasedFoo } //@[17:020) | └─ImportedVariableReferenceExpression { Variable = foo } diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.json b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.json index 416bc744eb4..a647ffb17ef 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.json +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "18183633158511667674" + "templateHash": "16966837921391267388" } }, "definitions": { @@ -52,6 +52,20 @@ } } }, + "fooType": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "metadata": { + "__bicep_deprecated!": "deprecated type", + "__bicep_imported_from!": { + "sourceTemplate": "modules/deprecations.bicep" + } + } + }, "pop": { "type": "string", "minLength": 3, @@ -66,6 +80,19 @@ { "namespace": "__bicep", "members": { + "fooFunc": { + "parameters": [], + "output": { + "type": "string", + "value": ":(" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "modules/deprecations.bicep" + }, + "__bicep_deprecated!": "deprecated func" + } + }, "greet": { "parameters": [ { @@ -78,10 +105,10 @@ "value": "[format('Hi, {0}!', parameters('name'))]" }, "metadata": { - "description": "Say hi to someone!", "__bicep_imported_from!": { "sourceTemplate": "modules/mod.bicep" - } + }, + "description": "Say hi to someone!" } } } @@ -114,6 +141,7 @@ ], "_3.foo": "bar", "foo": "[variables('_1.bar')]", + "fooVar": "", "refersToCopyVariable": "[variables('_2.copyVariable')]", "withInvalidIdentifier": "value" }, diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.sourcemap.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.sourcemap.bicep index 596e6c17b6f..dfcb4f16c3d 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.sourcemap.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.sourcemap.bicep @@ -46,7 +46,7 @@ import {foo, fizz, pop, greet} from 'modules/mod.bicep' //@ "metadata": { //@ "__bicep_imported_from!": { //@ "sourceTemplate": "modules/mod.bicep" -//@ } +//@ }, //@ } //@ } //@ "_1.bar": "[variables('_1.baz')]", @@ -86,8 +86,38 @@ import { //@ "refersToCopyVariable": "[variables('_2.copyVariable')]", } from 'modules/mod.json' +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' +//@ "fooType": { +//@ "type": "object", +//@ "properties": { +//@ "bar": { +//@ "type": "string" +//@ } +//@ }, +//@ "metadata": { +//@ "__bicep_deprecated!": "deprecated type", +//@ "__bicep_imported_from!": { +//@ "sourceTemplate": "modules/deprecations.bicep" +//@ } +//@ } +//@ }, +//@ "fooFunc": { +//@ "parameters": [], +//@ "output": { +//@ "type": "string", +//@ "value": ":(" +//@ }, +//@ "metadata": { +//@ "__bicep_imported_from!": { +//@ "sourceTemplate": "modules/deprecations.bicep" +//@ }, +//@ "__bicep_deprecated!": "deprecated func" +//@ } +//@ }, +//@ "description": "Say hi to someone!" +//@ "fooVar": "", + var aliasedFoo = foo -//@ "description": "Say hi to someone!", //@ "aliasedFoo": "[variables('foo')]", var aliasedBar = mod2.foo //@ "aliasedBar": "[variables('_3.foo')]", diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbolicnames.json b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbolicnames.json index 416bc744eb4..a647ffb17ef 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbolicnames.json +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbolicnames.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "18183633158511667674" + "templateHash": "16966837921391267388" } }, "definitions": { @@ -52,6 +52,20 @@ } } }, + "fooType": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "metadata": { + "__bicep_deprecated!": "deprecated type", + "__bicep_imported_from!": { + "sourceTemplate": "modules/deprecations.bicep" + } + } + }, "pop": { "type": "string", "minLength": 3, @@ -66,6 +80,19 @@ { "namespace": "__bicep", "members": { + "fooFunc": { + "parameters": [], + "output": { + "type": "string", + "value": ":(" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "modules/deprecations.bicep" + }, + "__bicep_deprecated!": "deprecated func" + } + }, "greet": { "parameters": [ { @@ -78,10 +105,10 @@ "value": "[format('Hi, {0}!', parameters('name'))]" }, "metadata": { - "description": "Say hi to someone!", "__bicep_imported_from!": { "sourceTemplate": "modules/mod.bicep" - } + }, + "description": "Say hi to someone!" } } } @@ -114,6 +141,7 @@ ], "_3.foo": "bar", "foo": "[variables('_1.bar')]", + "fooVar": "", "refersToCopyVariable": "[variables('_2.copyVariable')]", "withInvalidIdentifier": "value" }, diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbols.bicep index d77dbcfc78c..ad7d1fff670 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.symbols.bicep @@ -12,6 +12,11 @@ import { //@[02:22) Variable refersToCopyVariable. Type: (object | object | object | object | object | object | object | object | object | object)[]. Declaration start char: 2, length: 20 } from 'modules/mod.json' +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' +//@[26:33) TypeAlias fooType. Type: Type<{ bar: string }>. Declaration start char: 26, length: 7 +//@[18:24) Variable fooVar. Type: ''. Declaration start char: 18, length: 6 +//@[09:16) Function fooFunc. Type: () => string. Declaration start char: 9, length: 7 + var aliasedFoo = foo //@[04:14) Variable aliasedFoo. Type: 'quux'. Declaration start char: 0, length: 20 var aliasedBar = mod2.foo diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.syntax.bicep index c6d72e42173..1b1a8c60799 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.syntax.bicep @@ -1,5 +1,5 @@ import {foo, fizz, pop, greet} from 'modules/mod.bicep' -//@[00:407) ProgramSyntax +//@[00:478) ProgramSyntax //@[00:055) ├─CompileTimeImportDeclarationSyntax //@[00:006) | ├─Token(Identifier) |import| //@[07:030) | ├─ImportedSymbolsListSyntax @@ -67,6 +67,29 @@ import { //@[07:025) | └─Token(StringComplete) |'modules/mod.json'| //@[25:027) ├─Token(NewLine) |\n\n| +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' +//@[00:069) ├─CompileTimeImportDeclarationSyntax +//@[00:006) | ├─Token(Identifier) |import| +//@[07:035) | ├─ImportedSymbolsListSyntax +//@[07:008) | | ├─Token(LeftBrace) |{| +//@[09:016) | | ├─ImportedSymbolsListItemSyntax +//@[09:016) | | | └─IdentifierSyntax +//@[09:016) | | | └─Token(Identifier) |fooFunc| +//@[16:017) | | ├─Token(Comma) |,| +//@[18:024) | | ├─ImportedSymbolsListItemSyntax +//@[18:024) | | | └─IdentifierSyntax +//@[18:024) | | | └─Token(Identifier) |fooVar| +//@[24:025) | | ├─Token(Comma) |,| +//@[26:033) | | ├─ImportedSymbolsListItemSyntax +//@[26:033) | | | └─IdentifierSyntax +//@[26:033) | | | └─Token(Identifier) |fooType| +//@[34:035) | | └─Token(RightBrace) |}| +//@[36:069) | └─CompileTimeImportFromClauseSyntax +//@[36:040) | ├─Token(Identifier) |from| +//@[41:069) | └─StringSyntax +//@[41:069) | └─Token(StringComplete) |'modules/deprecations.bicep'| +//@[69:071) ├─Token(NewLine) |\n\n| + var aliasedFoo = foo //@[00:020) ├─VariableDeclarationSyntax //@[00:003) | ├─Token(Identifier) |var| diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.tokens.bicep index e2edf807072..e691ab0bdbd 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/main.tokens.bicep @@ -38,6 +38,19 @@ import { //@[07:25) StringComplete |'modules/mod.json'| //@[25:27) NewLine |\n\n| +import { fooFunc, fooVar, fooType } from 'modules/deprecations.bicep' +//@[00:06) Identifier |import| +//@[07:08) LeftBrace |{| +//@[09:16) Identifier |fooFunc| +//@[16:17) Comma |,| +//@[18:24) Identifier |fooVar| +//@[24:25) Comma |,| +//@[26:33) Identifier |fooType| +//@[34:35) RightBrace |}| +//@[36:40) Identifier |from| +//@[41:69) StringComplete |'modules/deprecations.bicep'| +//@[69:71) NewLine |\n\n| + var aliasedFoo = foo //@[00:03) Identifier |var| //@[04:14) Identifier |aliasedFoo| diff --git a/src/Bicep.Core.Samples/Files/baselines/Imports_LF/modules/deprecations.bicep b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/modules/deprecations.bicep new file mode 100644 index 00000000000..4df1d414595 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/Imports_LF/modules/deprecations.bicep @@ -0,0 +1,19 @@ +@deprecated('deprecated param') +param fooParam string? + +@export() +@deprecated('deprecated var') +var fooVar = '' + +@export() +@deprecated('deprecated func') +func fooFunc() string => ':(' + +@export() +@deprecated('deprecated type') +type fooType = { + bar: string +} + +@deprecated('deprecated output') +output fooOutput string = ':(' diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decorators.json b/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decorators.json index 900ea8fb383..e77f5c4a622 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decorators.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decorators.json @@ -41,6 +41,27 @@ "command": "editor.action.triggerParameterHints" } }, + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decoratorsPlusNamespace.json b/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decoratorsPlusNamespace.json index 330bc4b97af..f69eac41cd1 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decoratorsPlusNamespace.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidOutputs_CRLF/Completions/decoratorsPlusNamespace.json @@ -41,6 +41,27 @@ "command": "editor.action.triggerParameterHints" } }, + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecorators.json b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecorators.json index 586f48df3a5..e5c5f3f26a1 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecorators.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecorators.json @@ -20,6 +20,27 @@ "command": "editor.action.triggerParameterHints" } }, + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecoratorsPlusNamespace.json b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecoratorsPlusNamespace.json index a6427e7ab14..53c3f711cf9 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecoratorsPlusNamespace.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/intParameterDecoratorsPlusNamespace.json @@ -20,6 +20,27 @@ "command": "editor.action.triggerParameterHints" } }, + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecorators.json b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecorators.json index f042c8894b9..1fba4491cb3 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecorators.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecorators.json @@ -20,6 +20,27 @@ "command": "editor.action.triggerParameterHints" } }, + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecoratorsPlusNamespace.json b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecoratorsPlusNamespace.json index 30f8437f3c0..09a97a7df60 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecoratorsPlusNamespace.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidParameters_LF/Completions/stringParameterDecoratorsPlusNamespace.json @@ -20,6 +20,27 @@ "command": "editor.action.triggerParameterHints" } }, + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.diagnostics.bicep index dc125418add..8cf3ca2fa8e 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.diagnostics.bicep @@ -82,7 +82,7 @@ type objectWithInvalidPropertyDecorators = { barProp: string @allowed(['snap', 'crackle', 'pop']) -//@[03:010) [BCP297 (Error)] Function "allowed" cannot be used as a type decorator. (bicep https://aka.ms/bicep/core-diagnostics#BCP297) |allowed| +//@[03:010) [BCP410 (Error)] Function "allowed" cannot be used as a type property decorator. (bicep https://aka.ms/bicep/core-diagnostics#BCP410) |allowed| krispyProp: string } diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/description.json b/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/description.json index b98e256d515..c327726ee25 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/description.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/description.json @@ -1,4 +1,25 @@ [ + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/sysAndDescription.json b/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/sysAndDescription.json index a4ee373ad83..c9ae8ab2156 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/sysAndDescription.json +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidVariables_LF/Completions/sysAndDescription.json @@ -1,4 +1,25 @@ [ + { + "label": "deprecated", + "kind": "function", + "documentation": { + "kind": "markdown", + "value": "```bicep\ndeprecated([description: string]): any\n\n``` \n \n" + }, + "deprecated": false, + "preselect": false, + "sortText": "3_deprecated", + "insertTextFormat": "snippet", + "insertTextMode": "adjustIndentation", + "textEdit": { + "range": {}, + "newText": "deprecated($0)" + }, + "command": { + "title": "signature help", + "command": "editor.action.triggerParameterHints" + } + }, { "label": "description", "kind": "function", diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/child/deprecations.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/child/deprecations.bicep new file mode 100644 index 00000000000..4df1d414595 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/child/deprecations.bicep @@ -0,0 +1,19 @@ +@deprecated('deprecated param') +param fooParam string? + +@export() +@deprecated('deprecated var') +var fooVar = '' + +@export() +@deprecated('deprecated func') +func fooFunc() string => ':(' + +@export() +@deprecated('deprecated type') +type fooType = { + bar: string +} + +@deprecated('deprecated output') +output fooOutput string = ':(' diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.bicep index 0f4b6ec7ae9..8d7cb7f1e79 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.bicep @@ -389,3 +389,12 @@ module moduleWithNameof 'modulea.bicep' = { ] } } + +module deprecations 'child/deprecations.bicep' = { + name: 'deprecations' + params: { + fooParam: 'foo' + } +} + +var testDeprecated = deprecations.outputs.fooOutput diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.diagnostics.bicep index 044445f0cdf..4c0652d817e 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.diagnostics.bicep @@ -402,3 +402,15 @@ module moduleWithNameof 'modulea.bicep' = { } } +module deprecations 'child/deprecations.bicep' = { + name: 'deprecations' + params: { + fooParam: 'foo' +//@[04:12) [no-deprecated-dependencies (Warning)] Symbol 'fooParam' has been marked as deprecated, and should not be used. Reason: 'deprecated param'. (bicep core linter https://aka.ms/bicep/linter/no-deprecated-dependencies) |fooParam| + } +} + +var testDeprecated = deprecations.outputs.fooOutput +//@[04:18) [no-unused-vars (Warning)] Variable "testDeprecated" is declared but never used. (bicep core linter https://aka.ms/bicep/linter/no-unused-vars) |testDeprecated| +//@[21:51) [no-deprecated-dependencies (Warning)] Symbol 'fooOutput' has been marked as deprecated, and should not be used. Reason: 'deprecated output'. (bicep core linter https://aka.ms/bicep/linter/no-deprecated-dependencies) |deprecations.outputs.fooOutput| + diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.formatted.bicep index d7dbcfdeb0c..cb84cf1911e 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.formatted.bicep @@ -409,3 +409,12 @@ module moduleWithNameof 'modulea.bicep' = { ] } } + +module deprecations 'child/deprecations.bicep' = { + name: 'deprecations' + params: { + fooParam: 'foo' + } +} + +var testDeprecated = deprecations.outputs.fooOutput diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.ir.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.ir.bicep index 7c441508948..b793bb48ff0 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.ir.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.ir.bicep @@ -1,5 +1,5 @@ -//@[000:9426) ProgramExpression +//@[000:9601) ProgramExpression //@[000:0000) | ├─ResourceDependencyExpression [UNPARENTED] //@[000:0000) | | └─ModuleReferenceExpression [UNPARENTED] //@[000:0000) | ├─ResourceDependencyExpression [UNPARENTED] @@ -1305,3 +1305,21 @@ module moduleWithNameof 'modulea.bicep' = { } } +module deprecations 'child/deprecations.bicep' = { +//@[000:0116) ├─DeclaredModuleExpression +//@[049:0116) | ├─ObjectExpression + name: 'deprecations' +//@[002:0022) | | └─ObjectPropertyExpression +//@[002:0006) | | ├─StringLiteralExpression { Value = name } +//@[008:0022) | | └─StringLiteralExpression { Value = deprecations } + params: { +//@[010:0037) | └─ObjectExpression + fooParam: 'foo' +//@[004:0019) | └─ObjectPropertyExpression +//@[004:0012) | ├─StringLiteralExpression { Value = fooParam } +//@[014:0019) | └─StringLiteralExpression { Value = foo } + } +} + +var testDeprecated = deprecations.outputs.fooOutput + diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.json b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.json index 0cf0e93695d..c2b8d575050 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.json +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "15268885528473582426" + "templateHash": "2224252998897311111" } }, "parameters": { @@ -2132,6 +2132,96 @@ "[resourceId('Microsoft.Resources/deployments', 'secureModuleCondition')]", "[resourceId('Microsoft.Resources/deployments', 'withSpace')]" ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deprecations", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "fooParam": { + "value": "foo" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "dev", + "templateHash": "10582621891520661866" + }, + "__bicep_exported_variables!": [ + { + "name": "fooVar", + "metadata": { + "__bicep_deprecated!": "deprecated var" + } + } + ] + }, + "definitions": { + "fooType": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "metadata": { + "__bicep_export!": true, + "__bicep_deprecated!": "deprecated type" + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "fooFunc": { + "parameters": [], + "output": { + "type": "string", + "value": ":(" + }, + "metadata": { + "__bicep_export!": true, + "__bicep_deprecated!": "deprecated func" + } + } + } + } + ], + "parameters": { + "fooParam": { + "type": "string", + "nullable": true, + "metadata": { + "__bicep_deprecated!": "deprecated param" + } + } + }, + "variables": { + "fooVar": "" + }, + "resources": {}, + "outputs": { + "fooOutput": { + "type": "string", + "metadata": { + "__bicep_deprecated!": "deprecated output" + }, + "value": ":(" + } + } + } + } } ], "outputs": { diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.sourcemap.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.sourcemap.bicep index e2c8528565b..5b4d5f1e66e 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.sourcemap.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.sourcemap.bicep @@ -2496,7 +2496,7 @@ module moduleWithNameof 'modulea.bicep' = { //@ "[resourceId('Microsoft.Resources/deployments', 'secureModuleCondition')]", //@ "[resourceId('Microsoft.Resources/deployments', 'withSpace')]" //@ ] -//@ } +//@ }, name: 'nameofModule' //@ "name": "nameofModule", scope: resourceGroup(nameof(nameofModuleParam)) @@ -2530,3 +2530,102 @@ module moduleWithNameof 'modulea.bicep' = { } } +module deprecations 'child/deprecations.bicep' = { +//@ { +//@ "type": "Microsoft.Resources/deployments", +//@ "apiVersion": "2022-09-01", +//@ "properties": { +//@ "expressionEvaluationOptions": { +//@ "scope": "inner" +//@ }, +//@ "mode": "Incremental", +//@ "template": { +//@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", +//@ "languageVersion": "2.0", +//@ "contentVersion": "1.0.0.0", +//@ "metadata": { +//@ "_generator": { +//@ "name": "bicep", +//@ "version": "dev", +//@ "templateHash": "10582621891520661866" +//@ }, +//@ "__bicep_exported_variables!": [ +//@ { +//@ "name": "fooVar", +//@ "metadata": { +//@ "__bicep_deprecated!": "deprecated var" +//@ } +//@ } +//@ ] +//@ }, +//@ "definitions": { +//@ "fooType": { +//@ "type": "object", +//@ "properties": { +//@ "bar": { +//@ "type": "string" +//@ } +//@ }, +//@ "metadata": { +//@ "__bicep_export!": true, +//@ "__bicep_deprecated!": "deprecated type" +//@ } +//@ } +//@ }, +//@ "functions": [ +//@ { +//@ "namespace": "__bicep", +//@ "members": { +//@ "fooFunc": { +//@ "parameters": [], +//@ "output": { +//@ "type": "string", +//@ "value": ":(" +//@ }, +//@ "metadata": { +//@ "__bicep_export!": true, +//@ "__bicep_deprecated!": "deprecated func" +//@ } +//@ } +//@ } +//@ } +//@ ], +//@ "parameters": { +//@ "fooParam": { +//@ "type": "string", +//@ "nullable": true, +//@ "metadata": { +//@ "__bicep_deprecated!": "deprecated param" +//@ } +//@ } +//@ }, +//@ "variables": { +//@ "fooVar": "" +//@ }, +//@ "resources": {}, +//@ "outputs": { +//@ "fooOutput": { +//@ "type": "string", +//@ "metadata": { +//@ "__bicep_deprecated!": "deprecated output" +//@ }, +//@ "value": ":(" +//@ } +//@ } +//@ } +//@ } +//@ } + name: 'deprecations' +//@ "name": "deprecations", + params: { +//@ "parameters": { +//@ }, + fooParam: 'foo' +//@ "fooParam": { +//@ "value": "foo" +//@ } + } +} + +var testDeprecated = deprecations.outputs.fooOutput + diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbolicnames.json b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbolicnames.json index 3e017187bf6..cf16e925a4e 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbolicnames.json +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbolicnames.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "12632249060210513106" + "templateHash": "11100495281920546990" } }, "parameters": { @@ -2197,6 +2197,96 @@ "secureModuleCondition", "withSpace" ] + }, + "deprecations": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deprecations", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "fooParam": { + "value": "foo" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "dev", + "templateHash": "10582621891520661866" + }, + "__bicep_exported_variables!": [ + { + "name": "fooVar", + "metadata": { + "__bicep_deprecated!": "deprecated var" + } + } + ] + }, + "definitions": { + "fooType": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "metadata": { + "__bicep_export!": true, + "__bicep_deprecated!": "deprecated type" + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "fooFunc": { + "parameters": [], + "output": { + "type": "string", + "value": ":(" + }, + "metadata": { + "__bicep_export!": true, + "__bicep_deprecated!": "deprecated func" + } + } + } + } + ], + "parameters": { + "fooParam": { + "type": "string", + "nullable": true, + "metadata": { + "__bicep_deprecated!": "deprecated param" + } + } + }, + "variables": { + "fooVar": "" + }, + "resources": {}, + "outputs": { + "fooOutput": { + "type": "string", + "metadata": { + "__bicep_deprecated!": "deprecated output" + }, + "value": ":(" + } + } + } + } } }, "outputs": { diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbols.bicep index 2109e05ca99..986571c9329 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.symbols.bicep @@ -472,3 +472,14 @@ module moduleWithNameof 'modulea.bicep' = { } } +module deprecations 'child/deprecations.bicep' = { +//@[07:19) Module deprecations. Type: module. Declaration start char: 0, length: 116 + name: 'deprecations' + params: { + fooParam: 'foo' + } +} + +var testDeprecated = deprecations.outputs.fooOutput +//@[04:18) Variable testDeprecated. Type: string. Declaration start char: 0, length: 51 + diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.syntax.bicep index 1a9e2dc8a3b..9c60dda8621 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.syntax.bicep @@ -1,5 +1,5 @@ -//@[000:9426) ProgramSyntax +//@[000:9601) ProgramSyntax //@[000:0002) ├─Token(NewLine) |\r\n| @sys.description('this is deployTimeSuffix param') //@[000:0093) ├─ParameterDeclarationSyntax @@ -3537,6 +3537,67 @@ module moduleWithNameof 'modulea.bicep' = { //@[003:0005) | ├─Token(NewLine) |\r\n| } //@[000:0001) | └─Token(RightBrace) |}| -//@[001:0003) ├─Token(NewLine) |\r\n| +//@[001:0005) ├─Token(NewLine) |\r\n\r\n| + +module deprecations 'child/deprecations.bicep' = { +//@[000:0116) ├─ModuleDeclarationSyntax +//@[000:0006) | ├─Token(Identifier) |module| +//@[007:0019) | ├─IdentifierSyntax +//@[007:0019) | | └─Token(Identifier) |deprecations| +//@[020:0046) | ├─StringSyntax +//@[020:0046) | | └─Token(StringComplete) |'child/deprecations.bicep'| +//@[047:0048) | ├─Token(Assignment) |=| +//@[049:0116) | └─ObjectSyntax +//@[049:0050) | ├─Token(LeftBrace) |{| +//@[050:0052) | ├─Token(NewLine) |\r\n| + name: 'deprecations' +//@[002:0022) | ├─ObjectPropertySyntax +//@[002:0006) | | ├─IdentifierSyntax +//@[002:0006) | | | └─Token(Identifier) |name| +//@[006:0007) | | ├─Token(Colon) |:| +//@[008:0022) | | └─StringSyntax +//@[008:0022) | | └─Token(StringComplete) |'deprecations'| +//@[022:0024) | ├─Token(NewLine) |\r\n| + params: { +//@[002:0037) | ├─ObjectPropertySyntax +//@[002:0008) | | ├─IdentifierSyntax +//@[002:0008) | | | └─Token(Identifier) |params| +//@[008:0009) | | ├─Token(Colon) |:| +//@[010:0037) | | └─ObjectSyntax +//@[010:0011) | | ├─Token(LeftBrace) |{| +//@[011:0013) | | ├─Token(NewLine) |\r\n| + fooParam: 'foo' +//@[004:0019) | | ├─ObjectPropertySyntax +//@[004:0012) | | | ├─IdentifierSyntax +//@[004:0012) | | | | └─Token(Identifier) |fooParam| +//@[012:0013) | | | ├─Token(Colon) |:| +//@[014:0019) | | | └─StringSyntax +//@[014:0019) | | | └─Token(StringComplete) |'foo'| +//@[019:0021) | | ├─Token(NewLine) |\r\n| + } +//@[002:0003) | | └─Token(RightBrace) |}| +//@[003:0005) | ├─Token(NewLine) |\r\n| +} +//@[000:0001) | └─Token(RightBrace) |}| +//@[001:0005) ├─Token(NewLine) |\r\n\r\n| + +var testDeprecated = deprecations.outputs.fooOutput +//@[000:0051) ├─VariableDeclarationSyntax +//@[000:0003) | ├─Token(Identifier) |var| +//@[004:0018) | ├─IdentifierSyntax +//@[004:0018) | | └─Token(Identifier) |testDeprecated| +//@[019:0020) | ├─Token(Assignment) |=| +//@[021:0051) | └─PropertyAccessSyntax +//@[021:0041) | ├─PropertyAccessSyntax +//@[021:0033) | | ├─VariableAccessSyntax +//@[021:0033) | | | └─IdentifierSyntax +//@[021:0033) | | | └─Token(Identifier) |deprecations| +//@[033:0034) | | ├─Token(Dot) |.| +//@[034:0041) | | └─IdentifierSyntax +//@[034:0041) | | └─Token(Identifier) |outputs| +//@[041:0042) | ├─Token(Dot) |.| +//@[042:0051) | └─IdentifierSyntax +//@[042:0051) | └─Token(Identifier) |fooOutput| +//@[051:0053) ├─Token(NewLine) |\r\n| //@[000:0000) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.tokens.bicep index 6a5c211225a..5a12a9805d0 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Modules_CRLF/main.tokens.bicep @@ -2213,6 +2213,46 @@ module moduleWithNameof 'modulea.bicep' = { //@[003:005) NewLine |\r\n| } //@[000:001) RightBrace |}| -//@[001:003) NewLine |\r\n| +//@[001:005) NewLine |\r\n\r\n| + +module deprecations 'child/deprecations.bicep' = { +//@[000:006) Identifier |module| +//@[007:019) Identifier |deprecations| +//@[020:046) StringComplete |'child/deprecations.bicep'| +//@[047:048) Assignment |=| +//@[049:050) LeftBrace |{| +//@[050:052) NewLine |\r\n| + name: 'deprecations' +//@[002:006) Identifier |name| +//@[006:007) Colon |:| +//@[008:022) StringComplete |'deprecations'| +//@[022:024) NewLine |\r\n| + params: { +//@[002:008) Identifier |params| +//@[008:009) Colon |:| +//@[010:011) LeftBrace |{| +//@[011:013) NewLine |\r\n| + fooParam: 'foo' +//@[004:012) Identifier |fooParam| +//@[012:013) Colon |:| +//@[014:019) StringComplete |'foo'| +//@[019:021) NewLine |\r\n| + } +//@[002:003) RightBrace |}| +//@[003:005) NewLine |\r\n| +} +//@[000:001) RightBrace |}| +//@[001:005) NewLine |\r\n\r\n| + +var testDeprecated = deprecations.outputs.fooOutput +//@[000:003) Identifier |var| +//@[004:018) Identifier |testDeprecated| +//@[019:020) Assignment |=| +//@[021:033) Identifier |deprecations| +//@[033:034) Dot |.| +//@[034:041) Identifier |outputs| +//@[041:042) Dot |.| +//@[042:051) Identifier |fooOutput| +//@[051:053) NewLine |\r\n| //@[000:000) EndOfFile || diff --git a/src/Bicep.Core/Analyzers/Linter/Rules/NoDeprecatedDependenciesRule.cs b/src/Bicep.Core/Analyzers/Linter/Rules/NoDeprecatedDependenciesRule.cs new file mode 100644 index 00000000000..c404d7fd27e --- /dev/null +++ b/src/Bicep.Core/Analyzers/Linter/Rules/NoDeprecatedDependenciesRule.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Security.Cryptography.X509Certificates; +using Azure.Deployments.Templates.Export; +using Bicep.Core.CodeAction; +using Bicep.Core.Diagnostics; +using Bicep.Core.Parsing; +using Bicep.Core.Semantics; +using Bicep.Core.Semantics.Metadata; +using Bicep.Core.Semantics.Namespaces; +using Bicep.Core.Syntax; +using Bicep.Core.Syntax.Comparers; +using Bicep.Core.Syntax.Visitors; + +namespace Bicep.Core.Analyzers.Linter.Rules; + +public sealed class NoDeprecatedDependenciesRule : LinterRuleBase +{ + public new const string Code = "no-deprecated-dependencies"; + + public NoDeprecatedDependenciesRule() : base( + code: Code, + description: CoreResources.NoDeprecatedDependenciesRuleDescription, + LinterRuleCategory.BestPractice, + docUri: new Uri($"https://aka.ms/bicep/linter/{Code}"), + diagnosticStyling: DiagnosticStyling.ShowCodeDeprecated) + { } + + public override string FormatMessage(params object[] values) + => values.Length == 1 ? + string.Format(CoreResources.NoDeprecatedDependenciesRuleMessageFormat, values) : + string.Format(CoreResources.NoDeprecatedDependenciesRuleMessageFormatWithDescription, values); + + private IDiagnostic CreateDeprecationDiagnostic(TextSpan span, string type, DeprecationMetadata deprecation) + => deprecation.Description is null ? + this.CreateDiagnosticForSpan(DefaultDiagnosticLevel, span, type) : + this.CreateDiagnosticForSpan(DefaultDiagnosticLevel, span, type, deprecation.Description); + + public override IEnumerable AnalyzeInternal(SemanticModel model, DiagnosticLevel diagnosticLevel) + { + foreach (var module in model.Root.ModuleDeclarations) + { + if (module.TryGetBodyPropertyValue(LanguageConstants.ModuleParamsPropertyName) is ObjectSyntax paramsObj && + module.TryGetSemanticModel().TryUnwrap() is {} moduleModel) + { + foreach (var property in paramsObj.Properties) + { + if (property.TryGetKeyText() is {} key && + moduleModel.Parameters.TryGetValue(key, out var parameter) && + parameter.DeprecationMetadata is {} deprecation) + { + yield return CreateDeprecationDiagnostic(property.Key.Span, parameter.Name, deprecation); + } + } + } + } + + foreach (var outputAccess in SyntaxAggregator.AggregateByType(model.SourceFile.ProgramSyntax)) + { + if (outputAccess.BaseExpression is PropertyAccessSyntax propertyAccess && + propertyAccess.PropertyName.NameEquals(LanguageConstants.ModuleOutputsPropertyName) && + model.GetSymbolInfo(propertyAccess.BaseExpression) is ModuleSymbol module && + module.TryGetSemanticModel().TryUnwrap() is {} moduleModel && + moduleModel.Outputs.FirstOrDefault(x => outputAccess.PropertyName.NameEquals(x.Name)) is {} output && + output.DeprecationMetadata is {} deprecation) + { + yield return CreateDeprecationDiagnostic(outputAccess.Span, output.Name, deprecation); + } + } + + foreach (var import in model.Root.ImportedSymbols) + { + var sourceModel = import.SourceModel; + + if (import.OriginalSymbolName is {} originalName && + sourceModel.Exports.TryGetValue(originalName, out var export)&& + export.DeprecationMetadata is {} deprecation) + { + yield return CreateDeprecationDiagnostic(import.DeclaringImportedSymbolsListItem.Span, export.Name, deprecation); + } + } + } +} diff --git a/src/Bicep.Core/CodeAction/Fixes/DecoratorCodeFixProvider.cs b/src/Bicep.Core/CodeAction/Fixes/DecoratorCodeFixProvider.cs index 0656c2794c0..cd60427aa30 100644 --- a/src/Bicep.Core/CodeAction/Fixes/DecoratorCodeFixProvider.cs +++ b/src/Bicep.Core/CodeAction/Fixes/DecoratorCodeFixProvider.cs @@ -63,7 +63,8 @@ private bool IsTargetDecorator(DecoratorSyntax decoratorSyntax) OutputDeclarationSyntax => FunctionFlags.OutputDecorator, ExtensionDeclarationSyntax => FunctionFlags.ExtensionDecorator, MetadataDeclarationSyntax => FunctionFlags.MetadataDecorator, - TypeDeclarationSyntax or ObjectTypePropertySyntax => FunctionFlags.TypeDecorator, + TypeDeclarationSyntax => FunctionFlags.TypeDecorator, + ObjectTypePropertySyntax => FunctionFlags.TypePropertyDecorator, _ => FunctionFlags.AnyDecorator, }; diff --git a/src/Bicep.Core/CoreResources.Designer.cs b/src/Bicep.Core/CoreResources.Designer.cs index 7cc67ea4135..3d4dcbb0b45 100644 --- a/src/Bicep.Core/CoreResources.Designer.cs +++ b/src/Bicep.Core/CoreResources.Designer.cs @@ -420,6 +420,33 @@ internal static string NoDeploymentsResourcesRuleMessageFormat { } } + /// + /// Looks up a localized string similar to . + /// + internal static string NoDeprecatedDependenciesRuleDescription { + get { + return ResourceManager.GetString("NoDeprecatedDependenciesRuleDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string NoDeprecatedDependenciesRuleMessageFormat { + get { + return ResourceManager.GetString("NoDeprecatedDependenciesRuleMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string NoDeprecatedDependenciesRuleMessageFormatWithDescription { + get { + return ResourceManager.GetString("NoDeprecatedDependenciesRuleMessageFormatWithDescription", 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.. /// diff --git a/src/Bicep.Core/CoreResources.resx b/src/Bicep.Core/CoreResources.resx index 03df2d8fd31..caa63eefea4 100644 --- a/src/Bicep.Core/CoreResources.resx +++ b/src/Bicep.Core/CoreResources.resx @@ -481,6 +481,15 @@ Resource '{0}' of type '{1}' should instead be declared as a Bicep module. + + Use of deprecated dependencies should be avoided. + + + Symbol '{0}' has been marked as deprecated, and should not be used. + + + Symbol '{0}' has been marked as deprecated, and should not be used. Reason: '{1}'. + Asserts @@ -530,4 +539,4 @@ Secure outputs - + diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 740d86bfe91..70638a227fb 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -1820,6 +1820,18 @@ public Diagnostic MicrosoftGraphBuiltinDeprecatedSoon(ExtensionDeclarationSyntax public Diagnostic NameofInvalidOnUnnamedExpression() => CoreError( "BCP408", $"The \"{LanguageConstants.NameofFunctionName}\" function can only be used with an expression which has a name."); + + public Diagnostic CannotUseFunctionAsTypePropertyDecorator(string functionName) => CoreError( + "BCP410", + $"Function \"{functionName}\" cannot be used as a type property decorator."); + + public Diagnostic CannotDeprecateUnexportedDeclarations() => CoreError( + "BCP411", + $"This declaration cannot be marked as deprecated, because it has not been exported."); + + public Diagnostic CannotDeprecateParameterWithMandatoryValue() => CoreError( + "BCP412", + $"Parameters must either be nullable or have a default value to be marked as deprecated."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/Emit/EmitLimitationCalculator.cs b/src/Bicep.Core/Emit/EmitLimitationCalculator.cs index 53e70215730..5750c5ecf20 100644 --- a/src/Bicep.Core/Emit/EmitLimitationCalculator.cs +++ b/src/Bicep.Core/Emit/EmitLimitationCalculator.cs @@ -9,6 +9,7 @@ using Bicep.Core.Parsing; using Bicep.Core.Semantics; using Bicep.Core.Semantics.Metadata; +using Bicep.Core.Semantics.Namespaces; using Bicep.Core.Syntax; using Bicep.Core.Syntax.Visitors; using Bicep.Core.TypeSystem; @@ -55,6 +56,7 @@ public static EmitLimitationInfo Calculate(SemanticModel model) BlockResourceDerivedTypesThatDoNotDereferenceProperties(model, diagnostics); BlockSpreadInUnsupportedLocations(model, diagnostics); BlockExtendsWithoutFeatureFlagEnabled(model, diagnostics); + BlockDeprecationInUnsupportedCases(model, diagnostics); var paramAssignments = CalculateParameterAssignments(model, diagnostics); @@ -624,6 +626,28 @@ private static void BlockExtendsWithoutFeatureFlagEnabled(SemanticModel model, I } } + private static void BlockDeprecationInUnsupportedCases(SemanticModel model, IDiagnosticWriter diagnostics) + { + foreach (var declaration in model.SourceFile.ProgramSyntax.Declarations.OfType()) + { + if (SemanticModelHelper.TryGetDecoratorInNamespace(model, declaration, SystemNamespaceType.BuiltInName, "deprecated") is not {} deprecated) + { + continue; + } + + if (declaration is TypeDeclarationSyntax or FunctionDeclarationSyntax or VariableDeclarationSyntax && + SemanticModelHelper.TryGetDecoratorInNamespace(model, declaration, SystemNamespaceType.BuiltInName, "export") is null) + { + diagnostics.Write(deprecated, x => x.CannotDeprecateUnexportedDeclarations()); + } + + if (declaration is ParameterDeclarationSyntax parameter && SemanticModelHelper.IsParameterRequired(model, parameter)) + { + diagnostics.Write(deprecated, x => x.CannotDeprecateParameterWithMandatoryValue()); + } + } + } + private static void BlockAssertsWithoutExperimentalFeatures(SemanticModel model, IDiagnosticWriter diagnostics) { foreach (var assert in model.Root.AssertDeclarations) diff --git a/src/Bicep.Core/Emit/TemplateWriter.cs b/src/Bicep.Core/Emit/TemplateWriter.cs index b4819396401..d06f6bea9b2 100644 --- a/src/Bicep.Core/Emit/TemplateWriter.cs +++ b/src/Bicep.Core/Emit/TemplateWriter.cs @@ -16,6 +16,7 @@ using Bicep.Core.TypeSystem; using Bicep.Core.TypeSystem.Providers.Az; using Bicep.Core.TypeSystem.Types; +using Microsoft.Extensions.Azure; using Microsoft.WindowsAzure.ResourceStack.Common.Json; using Newtonsoft.Json.Linq; @@ -124,7 +125,7 @@ public void Write(SourceAwareJsonTextWriter writer) emitter.EmitProperty("contentVersion", "1.0.0.0"); - this.EmitMetadata(emitter, program.Metadata); + this.EmitMetadata(emitter, program); this.EmitTypeDefinitionsIfPresent(emitter, programTypes); @@ -228,11 +229,6 @@ private static ObjectExpression ApplyTypeModifiers(TypeDeclaringExpression expre result = result.MergeProperty("additionalProperties", ExpressionFactory.CreateBooleanLiteral(false, @sealed.SourceSyntax)); } - if (expression is DeclaredTypeExpression declaredTypeExpression && declaredTypeExpression.Exported is { } exported) - { - result = ApplyMetadataProperty(result, LanguageConstants.MetadataExportedPropertyName, ExpressionFactory.CreateBooleanLiteral(true, exported.SourceSyntax)); - } - foreach (var (modifier, propertyName) in new[] { (expression.Metadata, LanguageConstants.ParameterMetadataPropertyName), @@ -248,11 +244,24 @@ private static ObjectExpression ApplyTypeModifiers(TypeDeclaringExpression expre } } - return ApplyDescription(expression, result); + return ApplyMetadataProperties(expression, result); } - private static ObjectExpression ApplyDescription(DescribableExpression expression, ObjectExpression input) - => ApplyMetadataProperty(input, LanguageConstants.MetadataDescriptionPropertyName, expression.Description); + private static ObjectExpression ApplyMetadataProperties(DescribableExpression expression, ObjectExpression input, bool skipExportProperty = false) + { + input = ApplyMetadataProperty(input, LanguageConstants.MetadataDescriptionPropertyName, expression.Description); + if (!skipExportProperty && expression is IExportableExpression exportable && exportable.Exported is {}) + { + input = ApplyMetadataProperty(input, LanguageConstants.MetadataExportedPropertyName, ExpressionFactory.CreateBooleanLiteral(true, exportable.Exported.SourceSyntax)); + } + if (expression.DeprecationMetadata is {} deprecationMetadata) + { + var reason = deprecationMetadata.Description ?? ""; + input = ApplyMetadataProperty(input, LanguageConstants.MetadataDeprecatedPropertyName, new StringLiteralExpression(expression.SourceSyntax, reason)); + } + + return input; + } private static ObjectExpression ApplyMetadataProperty(ObjectExpression input, string propertyName, Expression? propertyValue) => propertyValue is not null ? input.MergeProperty(LanguageConstants.ParameterMetadataPropertyName, ExpressionFactory.CreateObject( @@ -317,31 +326,30 @@ private void EmitUserDefinedFunction(ExpressionEmitter emitter, DeclaredFunction if (function.Description is not null || function.Exported is not null || Context.ImportClosureInfo.ImportedSymbolOriginMetadata.ContainsKey(originMetadataLookupKey)) { - emitter.EmitObjectProperty(LanguageConstants.ParameterMetadataPropertyName, () => + var metadataObj = ExpressionFactory.CreateObject(ImmutableArray.Empty); + if (Context.ImportClosureInfo.ImportedSymbolOriginMetadata.TryGetValue(originMetadataLookupKey, out var originMetadata)) { - if (function.Description is not null) + var importedFromProps = new List { - emitter.EmitProperty(LanguageConstants.MetadataDescriptionPropertyName, function.Description); - } + ExpressionFactory.CreateObjectProperty( + LanguageConstants.ImportMetadataSourceTemplatePropertyName, + new StringLiteralExpression(null, originMetadata.SourceTemplateIdentifier)) + }; - if (function.Exported is not null) + if (!function.Name.Equals(originMetadata.OriginalName)) { - emitter.EmitProperty(LanguageConstants.MetadataExportedPropertyName, ExpressionFactory.CreateBooleanLiteral(true, function.Exported.SourceSyntax)); + importedFromProps.Add(ExpressionFactory.CreateObjectProperty( + LanguageConstants.ImportMetadataOriginalIdentifierPropertyName, + new StringLiteralExpression(null, originMetadata.OriginalName))); } - if (Context.ImportClosureInfo.ImportedSymbolOriginMetadata.TryGetValue(originMetadataLookupKey, out var originMetadata)) - { - emitter.EmitObjectProperty(LanguageConstants.MetadataImportedFromPropertyName, () => - { - emitter.EmitProperty(LanguageConstants.ImportMetadataSourceTemplatePropertyName, originMetadata.SourceTemplateIdentifier); + metadataObj = ApplyMetadataProperty(metadataObj, LanguageConstants.MetadataImportedFromPropertyName, ExpressionFactory.CreateObject(importedFromProps, null)); + } - if (!function.Name.Equals(originMetadata.OriginalName)) - { - emitter.EmitProperty(LanguageConstants.ImportMetadataOriginalIdentifierPropertyName, originMetadata.OriginalName); - } - }); - } - }); + foreach (var property in ApplyMetadataProperties(function, metadataObj).Properties) + { + emitter.EmitProperty(property); + } } }, function.SourceSyntax); } @@ -1259,7 +1267,7 @@ private void EmitResource(ExpressionEmitter emitter, DeclaredResourceExpression // Since we don't want to be mutating the body of the original ObjectSyntax, we create an placeholder body in place // and emit its properties to merge decorator properties. - foreach (var property in ApplyDescription(resource, ExpressionFactory.CreateObject(ImmutableArray.Empty)).Properties) + foreach (var property in ApplyMetadataProperties(resource, ExpressionFactory.CreateObject(ImmutableArray.Empty)).Properties) { emitter.EmitProperty(property); } @@ -1382,7 +1390,7 @@ private void EmitModuleForLocalDeploy(PositionTrackingJsonTextWriter jsonWriter, // Since we don't want to be mutating the body of the original ObjectSyntax, we create a placeholder body in place // and emit its properties to merge decorator properties. - foreach (var property in ApplyDescription(module, ExpressionFactory.CreateObject(ImmutableArray.Empty)).Properties) + foreach (var property in ApplyMetadataProperties(module, ExpressionFactory.CreateObject(ImmutableArray.Empty)).Properties) { emitter.EmitProperty(property); } @@ -1477,7 +1485,7 @@ private void EmitModule(PositionTrackingJsonTextWriter jsonWriter, DeclaredModul // Since we don't want to be mutating the body of the original ObjectSyntax, we create an placeholder body in place // and emit its properties to merge decorator properties. - foreach (var property in ApplyDescription(module, ExpressionFactory.CreateObject(ImmutableArray.Empty)).Properties) + foreach (var property in ApplyMetadataProperties(module, ExpressionFactory.CreateObject(ImmutableArray.Empty)).Properties) { emitter.EmitProperty(property); } @@ -1640,7 +1648,7 @@ private void EmitAssertsIfPresent(ExpressionEmitter emitter, ImmutableArray metadata) + private void EmitMetadata(ExpressionEmitter emitter, ProgramExpression program) { emitter.EmitObjectProperty("metadata", () => { @@ -1672,17 +1680,20 @@ private void EmitMetadata(ExpressionEmitter emitter, ImmutableArray { - emitter.EmitProperty("name", exportedVariable.Name); - if (exportedVariable.Description is string description) - { - emitter.EmitProperty(LanguageConstants.MetadataDescriptionPropertyName, description); + var variable = program.Variables.Single(x => LanguageConstants.IdentifierComparer.Equals(x.Name, exportedVariable.Name)); + + emitter.EmitProperty("name", variable.Name); + // emitting "__bicep_export!" is technically unnecessary here, because this is already a list of exported variables + foreach (var property in ApplyMetadataProperties(variable, ExpressionFactory.CreateObject(ImmutableArray.Empty), skipExportProperty: true).Properties) + { + emitter.EmitProperty(property); } }); } }); } - foreach (var item in metadata) + foreach (var item in program.Metadata) { emitter.EmitProperty(item.Name, item.Value); } diff --git a/src/Bicep.Core/Intermediate/Expression.cs b/src/Bicep.Core/Intermediate/Expression.cs index 6bcb6f674f4..3a7d9c54d80 100644 --- a/src/Bicep.Core/Intermediate/Expression.cs +++ b/src/Bicep.Core/Intermediate/Expression.cs @@ -16,6 +16,11 @@ public record IndexReplacementContext( ImmutableDictionary LocalReplacements, Expression Index); +public interface IExportableExpression +{ + Expression? Exported { get; } +} + [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] public abstract record Expression( SyntaxBase? SourceSyntax) @@ -370,7 +375,8 @@ public override void Accept(IExpressionVisitor visitor) public abstract record DescribableExpression( SyntaxBase? SourceSyntax, - Expression? Description + Expression? Description, + DeprecationMetadata? DeprecationMetadata ) : Expression(SourceSyntax) { } @@ -379,7 +385,7 @@ public record DeclaredMetadataExpression( string Name, Expression Value, Expression? Description = null -) : DescribableExpression(SourceSyntax, Description) +) : DescribableExpression(SourceSyntax, Description, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredMetadataExpression(this); @@ -393,7 +399,7 @@ public record DeclaredExtensionExpression( NamespaceSettings Settings, Expression? Config, Expression? Description = null -) : DescribableExpression(SourceSyntax, Description) +) : DescribableExpression(SourceSyntax, Description, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredExtensionExpression(this); @@ -410,8 +416,9 @@ public abstract record TypeDeclaringExpression( Expression? MaxLength, Expression? MinValue, Expression? MaxValue, - Expression? Sealed -) : DescribableExpression(SourceSyntax, Description) + Expression? Sealed, + DeprecationMetadata? DeprecationMetadata +) : DescribableExpression(SourceSyntax, Description, DeprecationMetadata) { } public record DeclaredParameterExpression( @@ -427,8 +434,9 @@ public record DeclaredParameterExpression( Expression? MinValue = null, Expression? MaxValue = null, Expression? Sealed = null, - Expression? AllowedValues = null -) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed) + Expression? AllowedValues = null, + DeprecationMetadata? DeprecationMetadata = null +) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed, DeprecationMetadata) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredParameterExpression(this); @@ -441,8 +449,9 @@ public record DeclaredVariableExpression( string Name, Expression Value, Expression? Description = null, - Expression? Exported = null -) : DescribableExpression(SourceSyntax, Description) + Expression? Exported = null, + DeprecationMetadata? DeprecationMetadata = null +) : DescribableExpression(SourceSyntax, Description, DeprecationMetadata), IExportableExpression { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredVariableExpression(this); @@ -462,8 +471,9 @@ public record DeclaredOutputExpression( Expression? MaxLength = null, Expression? MinValue = null, Expression? MaxValue = null, - Expression? Sealed = null -) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed) + Expression? Sealed = null, + DeprecationMetadata? DeprecationMetadata = null +) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed, DeprecationMetadata) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredOutputExpression(this); @@ -476,7 +486,7 @@ public record DeclaredAssertExpression( string Name, Expression Value, Expression? Description = null -) : DescribableExpression(SourceSyntax, Description) +) : DescribableExpression(SourceSyntax, Description, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredAssertExpression(this); @@ -492,7 +502,7 @@ public record DeclaredResourceExpression( Expression Body, ImmutableArray DependsOn, Expression? Description = null -) : DescribableExpression(SourceSyntax, Description) +) : DescribableExpression(SourceSyntax, Description, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredResourceExpression(this); @@ -507,7 +517,7 @@ public record DeclaredModuleExpression( Expression? Parameters, ImmutableArray DependsOn, Expression? Description = null -) : DescribableExpression(SourceSyntax, Description) +) : DescribableExpression(SourceSyntax, Description, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredModuleExpression(this); @@ -556,8 +566,9 @@ public record DeclaredFunctionExpression( string Name, Expression Lambda, Expression? Description = null, - Expression? Exported = null -) : DescribableExpression(SourceSyntax, Description) + Expression? Exported = null, + DeprecationMetadata? DeprecationMetadata = null +) : DescribableExpression(SourceSyntax, Description, DeprecationMetadata), IExportableExpression { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredFunctionExpression(this); @@ -627,8 +638,9 @@ public record DeclaredTypeExpression( Expression? MinValue = null, Expression? MaxValue = null, Expression? Sealed = null, - Expression? Exported = null -) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed) + Expression? Exported = null, + DeprecationMetadata? DeprecationMetadata = null +) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed, DeprecationMetadata), IExportableExpression { public override void Accept(IExpressionVisitor visitor) => visitor.VisitDeclaredTypeExpression(this); @@ -827,8 +839,9 @@ public record ObjectTypePropertyExpression( Expression? MaxLength = null, Expression? MinValue = null, Expression? MaxValue = null, - Expression? Sealed = null -) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed) + Expression? Sealed = null, + DeprecationMetadata? DeprecationMetadata = null +) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed, DeprecationMetadata) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitObjectTypePropertyExpression(this); @@ -845,7 +858,7 @@ public record ObjectTypeAdditionalPropertiesExpression( Expression? MinValue = null, Expression? MaxValue = null, Expression? Sealed = null -) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed) +) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitObjectTypeAdditionalPropertiesExpression(this); @@ -873,7 +886,7 @@ public record TupleTypeItemExpression( Expression? MinValue = null, Expression? MaxValue = null, Expression? Sealed = null -) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed) +) : TypeDeclaringExpression(SourceSyntax, Description, Metadata, Secure, MinLength, MaxLength, MinValue, MaxValue, Sealed, null) { public override void Accept(IExpressionVisitor visitor) => visitor.VisitTupleTypeItemExpression(this); diff --git a/src/Bicep.Core/LanguageConstants.cs b/src/Bicep.Core/LanguageConstants.cs index 125b9cf63d0..600c7d4cace 100644 --- a/src/Bicep.Core/LanguageConstants.cs +++ b/src/Bicep.Core/LanguageConstants.cs @@ -153,6 +153,7 @@ public static class LanguageConstants public const string MetadataResourceTypePropertyName = "resourceType"; public const string MetadataResourceDerivedTypePropertyName = "__bicep_resource_derived_type!"; public const string MetadataExportedPropertyName = "__bicep_export!"; + public const string MetadataDeprecatedPropertyName = "__bicep_deprecated!"; public const string MetadataImportedFromPropertyName = "__bicep_imported_from!"; public const string TemplateMetadataExportedVariablesName = "__bicep_exported_variables!"; public const string ImportMetadataSourceTemplatePropertyName = "sourceTemplate"; @@ -160,6 +161,7 @@ public static class LanguageConstants public const string BatchSizePropertyName = "batchSize"; public const string ExportPropertyName = "export"; public const string TypeDiscriminatorDecoratorName = "discriminator"; + public const string DeprecatedDecoratorName = "deprecated"; // module properties public const string ModuleParamsPropertyName = "params"; diff --git a/src/Bicep.Core/Semantics/ArmTemplateSemanticModel.cs b/src/Bicep.Core/Semantics/ArmTemplateSemanticModel.cs index 29a29724e8f..1626fa8ea71 100644 --- a/src/Bicep.Core/Semantics/ArmTemplateSemanticModel.cs +++ b/src/Bicep.Core/Semantics/ArmTemplateSemanticModel.cs @@ -20,6 +20,7 @@ using Bicep.Core.TypeSystem.Types; using Bicep.Core.Workspaces; using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; +using Microsoft.WindowsAzure.ResourceStack.Common.Json; using Newtonsoft.Json.Linq; namespace Bicep.Core.Semantics @@ -85,7 +86,8 @@ public ArmTemplateSemanticModel(ArmTemplateFile sourceFile) parameterProperty.Key, type, parameterProperty.Value.DefaultValue is null && !TypeHelper.IsNullable(type.Type), - GetMostSpecificDescription(parameterProperty.Value)); + GetMostSpecificDescription(parameterProperty.Value), + GetDeprecationMetadata(parameterProperty.Value)); }, LanguageConstants.IdentifierComparer); }); @@ -103,7 +105,8 @@ public ArmTemplateSemanticModel(ArmTemplateFile sourceFile) .Select(outputProperty => new OutputMetadata( outputProperty.Key, GetType(outputProperty.Value), - TryGetMetadataDescription(outputProperty.Value.Metadata))) + TryGetMetadataDescription(outputProperty.Value.Metadata), + GetDeprecationMetadata(outputProperty.Value.Metadata))) .ToImmutableArray(); }); } @@ -187,6 +190,24 @@ private ITypeReference GetType(ITemplateSchemaNode schemaNode, bool allowLooseAs return null; } + private DeprecationMetadata? GetDeprecationMetadata(ITemplateSchemaNode schemaNode) + => GetMetadata(schemaNode) is JObject metadataObject ? GetDeprecationMetadata(metadataObject) : null; + + private DeprecationMetadata? GetDeprecationMetadata(TemplateGenericProperty? property) + => property?.Value is JObject metadataObject ? GetDeprecationMetadata(metadataObject) : null; + + private static DeprecationMetadata? GetDeprecationMetadata(JObject metadataObject) + { + if (metadataObject.TryGetValue(LanguageConstants.MetadataDeprecatedPropertyName, out var deprecationData) && + deprecationData is JValue { Value: string reason }) + { + // null is written as "", so we convert "" back to null + return new(reason == "" ? null : reason); + } + + return null; + } + private JToken? GetMetadata(ITemplateSchemaNode schemaNode) { try @@ -222,7 +243,7 @@ private ImmutableSortedDictionary FindExports() if (template.Definitions is { } typeDefinitions) { exports.AddRange(typeDefinitions.Where(kvp => IsExported(kvp.Value)) - .Select(kvp => new ExportedTypeMetadata(kvp.Key, AsTypeType(GetType(kvp.Value)), GetMostSpecificDescription(kvp.Value)))); + .Select(kvp => new ExportedTypeMetadata(kvp.Key, AsTypeType(GetType(kvp.Value)), GetMostSpecificDescription(kvp.Value), GetDeprecationMetadata(kvp.Value)))); } if (template.Functions is { } userDefinedFunctions) @@ -246,7 +267,8 @@ private ImmutableSortedDictionary FindExports() .Select(p => new ExportedFunctionParameterMetadata(p.Name?.Value ?? string.Empty, GetType(p), GetMostSpecificDescription(p))) .ToImmutableArray(), Return: new(GetType(kvp.Value.Output), GetMostSpecificDescription(kvp.Value.Output)), - Description: kvp.Value.Metadata?.Value is JObject metadataObject ? GetDescriptionFromMetadata(metadataObject) : null))); + Description: kvp.Value.Metadata?.Value is JObject metadataObject ? GetDescriptionFromMetadata(metadataObject) : null, + DeprecationMetadata: GetDeprecationMetadata(kvp.Value.Metadata)))); } } @@ -262,7 +284,14 @@ private ImmutableSortedDictionary FindExports() nameToken is JValue { Value: string name } && evaluator.TryGetEvaluatedVariableValue(name) is JToken evaluatedValue) { - exports.Add(new ExportedVariableMetadata(name, SystemNamespaceType.ConvertJsonToBicepType(evaluatedValue), GetDescription(exportedVariableObject))); + var exportedVariableMetadata = new JObject(); + if (exportedVariableObject.TryGetValue(LanguageConstants.ParameterMetadataPropertyName, StringComparison.OrdinalIgnoreCase, out var metadataToken) && + metadataToken is JObject metadataObject) + { + exportedVariableMetadata = metadataObject; + } + + exports.Add(new ExportedVariableMetadata(name, SystemNamespaceType.ConvertJsonToBicepType(evaluatedValue), GetDescription(exportedVariableMetadata), GetDeprecationMetadata(exportedVariableMetadata))); } } } diff --git a/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs b/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs index 8eaecd55982..6e00024e9ef 100644 --- a/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs +++ b/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs @@ -35,7 +35,7 @@ public DeclaredFunctionSymbol(ISymbolContext context, string name, FunctionDecla private FunctionOverload GetFunctionOverload() { var builder = new FunctionOverloadBuilder(this.Name); - if (DescriptionHelper.TryGetFromDecorator(Context.Binder, Context.TypeManager, DeclaringFunction) is { } description) + if (Context.SemanticModel.Facts.Description.Get(DeclaringFunction) is { } description) { builder.WithGenericDescription(description); } diff --git a/src/Bicep.Core/Semantics/DescriptionHelper.cs b/src/Bicep.Core/Semantics/DescriptionHelper.cs index 69a08ef6b11..367e302d714 100644 --- a/src/Bicep.Core/Semantics/DescriptionHelper.cs +++ b/src/Bicep.Core/Semantics/DescriptionHelper.cs @@ -13,38 +13,6 @@ namespace Bicep.Core.Semantics { public static class DescriptionHelper { - /// - /// Retrieves description for a given syntax from a @description decorator - /// - public static string? TryGetFromDecorator(SemanticModel semanticModel, DecorableSyntax decorable) - => TryGetFromDecorator(semanticModel.Binder, semanticModel.TypeManager, decorable); - - /// - /// Retrieves description for a given syntax from a @description decorator - /// - public static string? TryGetFromDecorator(IBinder binder, ITypeManager typeManager, DecorableSyntax decorable) - { - var decorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, - typeManager.GetDeclaredType, - decorable, - SystemNamespaceType.BuiltInName, - LanguageConstants.MetadataDescriptionPropertyName); - - if (decorator is not null && - decorator.Arguments.FirstOrDefault()?.Expression is StringSyntax stringSyntax - && stringSyntax.TryGetLiteralValue() is string description) - { - if (stringSyntax.IsVerbatimString()) - { - return StringUtils.NormalizeIndent(description); - } - - return description; - } - - return null; - } - /// /// Retrieves description for a given module represented by a semantic model (bicep or json ARM) /// diff --git a/src/Bicep.Core/Semantics/FactsCache.cs b/src/Bicep.Core/Semantics/FactsCache.cs new file mode 100644 index 00000000000..1a0d4252b98 --- /dev/null +++ b/src/Bicep.Core/Semantics/FactsCache.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Bicep.Core.Parsing; +using Bicep.Core.Semantics.Metadata; +using Bicep.Core.Semantics.Namespaces; +using Bicep.Core.Syntax; + +namespace Bicep.Core.Semantics; + +public interface IFactCache + where TType : notnull +{ + TFact Get(TType value); +} + +public interface IFactCache +{ + TFact Get(); +} + +public interface IFactsCache +{ + IFactCache DeprecationMetadata { get; } + + IFactCache Description { get; } +} + +public class FactsCache : IFactsCache +{ + private readonly SemanticModel model; + + public FactsCache(SemanticModel model) + { + this.model = model; + this.DeprecationMetadata = new FactCache(GetDeprecationMetadata); + this.Description = new FactCache(GetDescription); + } + + private class FactCache<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TFact> : IFactCache + { + private readonly Lazy cache; + + public FactCache(Func lookupFunc) + { + cache = new(lookupFunc, LazyThreadSafetyMode.PublicationOnly); + } + + public TFact Get() + => cache.Value; + } + + private class FactCache : IFactCache + where TType : notnull + { + public FactCache(Func lookupFunc) + { + this.lookupFunc = lookupFunc; + } + + private readonly ConcurrentDictionary cache = new(); + private readonly Func lookupFunc; + + public TFact Get(TType key) + => cache.GetOrAdd(key, lookupFunc); + } + + public IFactCache DeprecationMetadata { get; } + + public IFactCache Description { get; } + + private DeprecationMetadata? GetDeprecationMetadata(SyntaxBase syntax) + { + if (syntax is not DecorableSyntax decorable) + { + return null; + } + + var decorator = SemanticModelHelper.TryGetDecoratorInNamespace(model.Binder, model.GetDeclaredType, decorable, SystemNamespaceType.BuiltInName, LanguageConstants.DeprecatedDecoratorName); + var reason = TryGetNormalizedStringValue(decorator?.Arguments.FirstOrDefault()?.Expression); + + return decorator is {} ? new(reason) : null; + } + + private string? GetDescription(SyntaxBase syntax) + { + if (syntax is not DecorableSyntax decorable) + { + return null; + } + + var decorator = SemanticModelHelper.TryGetDecoratorInNamespace(model.Binder, model.GetDeclaredType, decorable, SystemNamespaceType.BuiltInName, LanguageConstants.MetadataDescriptionPropertyName); + return TryGetNormalizedStringValue(decorator?.Arguments.FirstOrDefault()?.Expression); + } + + private static string? TryGetNormalizedStringValue(SyntaxBase? syntax) + { + if (syntax is StringSyntax stringSyntax && stringSyntax.TryGetLiteralValue() is string value) + { + return stringSyntax.IsVerbatimString() ? StringUtils.NormalizeIndent(value) : value; + } + + return null; + } +} \ No newline at end of file diff --git a/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs b/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs index 2776554ec7a..4ad62910471 100644 --- a/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs +++ b/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs @@ -6,6 +6,8 @@ namespace Bicep.Core.Semantics.Metadata; +public record DeprecationMetadata(string? Description); + public enum ExportMetadataKind { Error = 0, @@ -14,20 +16,20 @@ public enum ExportMetadataKind Function, } -public abstract record ExportMetadata(ExportMetadataKind Kind, string Name, ITypeReference TypeReference, string? Description); +public abstract record ExportMetadata(ExportMetadataKind Kind, string Name, ITypeReference TypeReference, string? Description, DeprecationMetadata? DeprecationMetadata); -public record ExportedTypeMetadata(string Name, ITypeReference TypeReference, string? Description) - : ExportMetadata(ExportMetadataKind.Type, Name, TypeReference, Description); +public record ExportedTypeMetadata(string Name, ITypeReference TypeReference, string? Description, DeprecationMetadata? DeprecationMetadata) + : ExportMetadata(ExportMetadataKind.Type, Name, TypeReference, Description, DeprecationMetadata); -public record ExportedVariableMetadata(string Name, ITypeReference TypeReference, string? Description) - : ExportMetadata(ExportMetadataKind.Variable, Name, TypeReference, Description); +public record ExportedVariableMetadata(string Name, ITypeReference TypeReference, string? Description, DeprecationMetadata? DeprecationMetadata) + : ExportMetadata(ExportMetadataKind.Variable, Name, TypeReference, Description, DeprecationMetadata); public record ExportedFunctionParameterMetadata(string Name, ITypeReference TypeReference, string? Description); public record ExportedFunctionReturnMetadata(ITypeReference TypeReference, string? Description); -public record ExportedFunctionMetadata(string Name, ImmutableArray Parameters, ExportedFunctionReturnMetadata Return, string? Description) - : ExportMetadata(ExportMetadataKind.Function, Name, new LambdaType(Parameters.Select(md => md.TypeReference).ToImmutableArray(), [], Return.TypeReference), Description); +public record ExportedFunctionMetadata(string Name, ImmutableArray Parameters, ExportedFunctionReturnMetadata Return, string? Description, DeprecationMetadata? DeprecationMetadata) + : ExportMetadata(ExportMetadataKind.Function, Name, new LambdaType(Parameters.Select(md => md.TypeReference).ToImmutableArray(), [], Return.TypeReference), Description, DeprecationMetadata); public record DuplicatedExportMetadata(string Name, ImmutableArray ExportKindsWithSameName) - : ExportMetadata(ExportMetadataKind.Error, Name, ErrorType.Empty(), $"The name \"{Name}\" is ambiguous because it refers to exports of the following kinds: {string.Join(", ", ExportKindsWithSameName)}."); + : ExportMetadata(ExportMetadataKind.Error, Name, ErrorType.Empty(), $"The name \"{Name}\" is ambiguous because it refers to exports of the following kinds: {string.Join(", ", ExportKindsWithSameName)}.", null); diff --git a/src/Bicep.Core/Semantics/Metadata/OutputMetadata.cs b/src/Bicep.Core/Semantics/Metadata/OutputMetadata.cs index 7fccfa9a657..d68b430f95a 100644 --- a/src/Bicep.Core/Semantics/Metadata/OutputMetadata.cs +++ b/src/Bicep.Core/Semantics/Metadata/OutputMetadata.cs @@ -4,7 +4,7 @@ namespace Bicep.Core.Semantics.Metadata { - public record OutputMetadata(string Name, ITypeReference TypeReference, string? Description) + public record OutputMetadata(string Name, ITypeReference TypeReference, string? Description, DeprecationMetadata? DeprecationMetadata) { } } diff --git a/src/Bicep.Core/Semantics/Metadata/ParameterMetadata.cs b/src/Bicep.Core/Semantics/Metadata/ParameterMetadata.cs index 296c7d5aaba..86891ec5cd1 100644 --- a/src/Bicep.Core/Semantics/Metadata/ParameterMetadata.cs +++ b/src/Bicep.Core/Semantics/Metadata/ParameterMetadata.cs @@ -4,7 +4,7 @@ namespace Bicep.Core.Semantics.Metadata { - public record ParameterMetadata(string Name, ITypeReference TypeReference, bool IsRequired, string? Description) + public record ParameterMetadata(string Name, ITypeReference TypeReference, bool IsRequired, string? Description, DeprecationMetadata? DeprecationMetadata) { } } diff --git a/src/Bicep.Core/Semantics/NameBindingVisitor.cs b/src/Bicep.Core/Semantics/NameBindingVisitor.cs index 0630110303b..970f791ad40 100644 --- a/src/Bicep.Core/Semantics/NameBindingVisitor.cs +++ b/src/Bicep.Core/Semantics/NameBindingVisitor.cs @@ -214,7 +214,7 @@ public override void VisitOutputDeclarationSyntax(OutputDeclarationSyntax syntax this.VisitNodes(syntax.LeadingNodes); this.Visit(syntax.Keyword); this.Visit(syntax.Name); - allowedFlags = FunctionFlags.TypeDecorator; + allowedFlags = FunctionFlags.TypePropertyDecorator; this.Visit(syntax.Type); allowedFlags = FunctionFlags.OutputDecorator; this.Visit(syntax.Assignment); @@ -240,7 +240,7 @@ public override void VisitParameterDeclarationSyntax(ParameterDeclarationSyntax this.VisitNodes(syntax.LeadingNodes); this.Visit(syntax.Keyword); this.Visit(syntax.Name); - allowedFlags = FunctionFlags.TypeDecorator; + allowedFlags = FunctionFlags.TypePropertyDecorator; this.Visit(syntax.Type); allowedFlags = FunctionFlags.ParamDefaultsOnly; this.Visit(syntax.Modifier); @@ -253,6 +253,7 @@ public override void VisitTypeDeclarationSyntax(TypeDeclarationSyntax syntax) this.VisitNodes(syntax.LeadingNodes); this.Visit(syntax.Keyword); this.Visit(syntax.Name); + allowedFlags = FunctionFlags.TypePropertyDecorator; this.Visit(syntax.Value); allowedFlags = FunctionFlags.Default; } diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs index a1522230379..a10e7423cef 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs @@ -1869,6 +1869,22 @@ not WildcardImportSymbol and } }) .Build(); + + yield return new DecoratorBuilder(LanguageConstants.DeprecatedDecoratorName) + .WithDescription("Indicates that a parameter, output or type is deprecated.") + .WithOptionalParameter("description", LanguageConstants.String, "A description to give details about the deprecation.") + .WithFlags(FunctionFlags.DeprecatableSyntaxDecorator) + .WithEvaluator((functionCall, decorated) => + { + if (decorated is DescribableExpression describable) + { + var reason = functionCall.Parameters.FirstOrDefault() is StringLiteralExpression stringExpression ? stringExpression.Value : null; + return describable with { DeprecationMetadata = new(reason) }; + } + + return decorated; + }) + .Build(); } foreach (var decorator in GetAlwaysPermittedDecorators()) diff --git a/src/Bicep.Core/Semantics/SemanticModel.cs b/src/Bicep.Core/Semantics/SemanticModel.cs index 10a53a4093d..2ce1910ade1 100644 --- a/src/Bicep.Core/Semantics/SemanticModel.cs +++ b/src/Bicep.Core/Semantics/SemanticModel.cs @@ -27,6 +27,7 @@ namespace Bicep.Core.Semantics { public class SemanticModel : ISemanticModel { + public FactsCache Facts { get; } private readonly Lazy emitLimitationInfoLazy; private readonly Lazy symbolHierarchyLazy; private readonly Lazy resourceAncestorsLazy; @@ -47,6 +48,7 @@ public class SemanticModel : ISemanticModel public SemanticModel(Compilation compilation, BicepSourceFile sourceFile) { + this.Facts = new(this); this.Compilation = compilation; this.SourceFile = sourceFile; this.Configuration = compilation.ConfigurationManager.GetConfiguration(sourceFile.FileUri); @@ -111,18 +113,19 @@ public SemanticModel(Compilation compilation, BicepSourceFile sourceFile) foreach (var param in this.Root.ParameterDeclarations.DistinctBy(p => p.Name)) { - var description = DescriptionHelper.TryGetFromDecorator(this, param.DeclaringParameter); + var description = Facts.Description.Get(param.DeclaringParameter); + var deprecation = Facts.DeprecationMetadata.Get(param.DeclaringParameter); var isRequired = SyntaxHelper.TryGetDefaultValue(param.DeclaringParameter) == null && !TypeHelper.IsNullable(param.Type); if (param.Type is ResourceType resourceType) { // Resource type parameters are a special case, we need to convert to a dedicated // type so we can compare differently for assignment. var type = new UnresolvedResourceType(resourceType.TypeReference); - parameters.Add(param.Name, new ParameterMetadata(param.Name, type, isRequired, description)); + parameters.Add(param.Name, new ParameterMetadata(param.Name, type, isRequired, description, deprecation)); } else { - parameters.Add(param.Name, new ParameterMetadata(param.Name, param.Type, isRequired, description)); + parameters.Add(param.Name, new ParameterMetadata(param.Name, param.Type, isRequired, description, deprecation)); } } @@ -139,17 +142,18 @@ public SemanticModel(Compilation compilation, BicepSourceFile sourceFile) foreach (var output in this.Root.OutputDeclarations.DistinctBy(o => o.Name)) { - var description = DescriptionHelper.TryGetFromDecorator(this, output.DeclaringOutput); + var description = Facts.Description.Get(output.DeclaringSyntax); + var deprecation = Facts.DeprecationMetadata.Get(output.DeclaringSyntax); if (output.Type is ResourceType resourceType) { // Resource type parameters are a special case, we need to convert to a dedicated // type so we can compare differently for assignment and code generation. var type = new UnresolvedResourceType(resourceType.TypeReference); - outputs.Add(new OutputMetadata(output.Name, type, description)); + outputs.Add(new OutputMetadata(output.Name, type, description, deprecation)); } else { - outputs.Add(new OutputMetadata(output.Name, output.Type, description)); + outputs.Add(new OutputMetadata(output.Name, output.Type, description, deprecation)); } } @@ -168,18 +172,19 @@ public bool HasAuxiliaryFileReference(Uri uri) private IEnumerable FindExportedTypes() => Root.TypeDeclarations .Where(t => t.IsExported()) - .Select(t => new ExportedTypeMetadata(t.Name, t.Type, DescriptionHelper.TryGetFromDecorator(this, t.DeclaringType))); + .Select(t => new ExportedTypeMetadata(t.Name, t.Type, Facts.Description.Get(t.DeclaringType), Facts.DeprecationMetadata.Get(t.DeclaringType))); private IEnumerable FindExportedVariables() => Root.VariableDeclarations .Where(v => v.IsExported()) - .Select(v => new ExportedVariableMetadata(v.Name, v.Type, DescriptionHelper.TryGetFromDecorator(this, v.DeclaringVariable))); + .Select(v => new ExportedVariableMetadata(v.Name, v.Type, Facts.Description.Get(v.DeclaringVariable), Facts.DeprecationMetadata.Get(v.DeclaringVariable))); private IEnumerable FindExportedFunctions() => Root.FunctionDeclarations .Where(f => f.IsExported()) .Select(f => new ExportedFunctionMetadata(f.Name, f.Overload.FixedParameters.Select(p => new ExportedFunctionParameterMetadata(p.Name, p.Type, p.Description)).ToImmutableArray(), new(f.Overload.TypeSignatureSymbol, null), - DescriptionHelper.TryGetFromDecorator(this, f.DeclaringFunction))); + Facts.Description.Get(f.DeclaringFunction), + Facts.DeprecationMetadata.Get(f.DeclaringFunction))); private static void TraceBuildOperation(BicepSourceFile sourceFile, IFeatureProvider features, RootConfiguration configuration) { diff --git a/src/Bicep.Core/Semantics/SemanticModelHelper.cs b/src/Bicep.Core/Semantics/SemanticModelHelper.cs index 69f34f03726..0d9efc3a603 100644 --- a/src/Bicep.Core/Semantics/SemanticModelHelper.cs +++ b/src/Bicep.Core/Semantics/SemanticModelHelper.cs @@ -3,6 +3,9 @@ using Bicep.Core.Diagnostics; using Bicep.Core.Navigation; +using Bicep.Core.Parsing; +using Bicep.Core.Semantics.Metadata; +using Bicep.Core.Semantics.Namespaces; using Bicep.Core.Syntax; using Bicep.Core.Syntax.Visitors; using Bicep.Core.TypeSystem; @@ -106,5 +109,10 @@ private static ResultWithDiagnostic TryGetSourceFile(IArtifactFileL return new(sourceFile); } + + public static bool IsParameterRequired(SemanticModel model, ParameterDeclarationSyntax parameter) + => model.GetSymbolInfo(parameter) is ParameterSymbol parameterSymbol && + SyntaxHelper.TryGetDefaultValue(parameter) is null && + !TypeHelper.IsNullable(parameterSymbol.Type); } } diff --git a/src/Bicep.Core/Semantics/SymbolExtensions.cs b/src/Bicep.Core/Semantics/SymbolExtensions.cs index 8d6eeabe9bd..476e32b1e7c 100644 --- a/src/Bicep.Core/Semantics/SymbolExtensions.cs +++ b/src/Bicep.Core/Semantics/SymbolExtensions.cs @@ -89,7 +89,7 @@ public static bool CanBeReferenced(this DeclaredSymbol declaredSymbol) => declaredSymbol is not OutputSymbol and not MetadataSymbol; public static string? TryGetDescriptionFromDecorator(this DeclaredSymbol symbol) - => symbol.DeclaringSyntax is DecorableSyntax decorableSyntax ? DescriptionHelper.TryGetFromDecorator(symbol.Context.Compilation.GetSemanticModel(symbol.Context.SourceFile), decorableSyntax) : null; + => symbol.Context.SemanticModel.Facts.Description.Get(symbol.DeclaringSyntax); public static DecoratorSyntax? TryGetDecorator(this Symbol symbol, string @namespace, string decoratorName) => symbol is DeclaredSymbol declaredSymbol && declaredSymbol.DeclaringSyntax is DecorableSyntax decorableSyntax ? diff --git a/src/Bicep.Core/Semantics/SymbolValidator.cs b/src/Bicep.Core/Semantics/SymbolValidator.cs index e2f5d15978b..ce8356ba9df 100644 --- a/src/Bicep.Core/Semantics/SymbolValidator.cs +++ b/src/Bicep.Core/Semantics/SymbolValidator.cs @@ -128,6 +128,11 @@ private static Symbol ResolveFunctionFlags(FunctionFlags allowedFlags, FunctionS return new ErrorSymbol(DiagnosticBuilder.ForPosition(span).CannotUseFunctionAsParameterDecorator(functionSymbol.Name)); } + if (!functionFlags.HasFlag(FunctionFlags.TypePropertyDecorator) && allowedFlags.HasFlag(FunctionFlags.TypePropertyDecorator)) + { + return new ErrorSymbol(DiagnosticBuilder.ForPosition(span).CannotUseFunctionAsTypePropertyDecorator(functionSymbol.Name)); + } + if (!functionFlags.HasFlag(FunctionFlags.TypeDecorator) && allowedFlags.HasFlag(FunctionFlags.TypeDecorator)) { return new ErrorSymbol(DiagnosticBuilder.ForPosition(span).CannotUseFunctionAsTypeDecorator(functionSymbol.Name)); diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 4ab117b8095..557edbe0b0d 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -30,16 +30,16 @@ public class DeclaredTypeManager private readonly ConcurrentDictionary userDefinedTypeReferences = new(); private readonly ConcurrentDictionary> reifiedTypes = new(); private readonly Lazy>> typeCycles; - private readonly ITypeManager typeManager; private readonly IBinder binder; + private readonly SemanticModel model; private readonly IFeatureProvider features; private readonly ResourceDerivedTypeResolver resourceDerivedTypeResolver; - public DeclaredTypeManager(ITypeManager typeManager, IBinder binder, IFeatureProvider features) + public DeclaredTypeManager(SemanticModel model, IBinder binder) { - this.typeManager = typeManager; this.binder = binder; - this.features = features; + this.model = model; + this.features = model.Features; this.resourceDerivedTypeResolver = new(binder); this.typeCycles = new(() => CyclicTypeCheckVisitor.FindCycles( binder, @@ -100,7 +100,7 @@ public DeclaredTypeManager(ITypeManager typeManager, IBinder binder, IFeaturePro return GetExtensionType(extension); case MetadataDeclarationSyntax metadata: - return new DeclaredTypeAssignment(this.typeManager.GetTypeInfo(metadata.Value), metadata); + return new DeclaredTypeAssignment(model.TypeManager.GetTypeInfo(metadata.Value), metadata); case ParameterDeclarationSyntax parameter: return GetParameterType(parameter); @@ -145,7 +145,7 @@ public DeclaredTypeManager(ITypeManager typeManager, IBinder binder, IFeaturePro return GetResourceAccessType(resourceAccess); case LocalVariableSyntax localVariable: - return new DeclaredTypeAssignment(this.typeManager.GetTypeInfo(localVariable), localVariable); + return new DeclaredTypeAssignment(model.TypeManager.GetTypeInfo(localVariable), localVariable); case FunctionCallSyntax functionCall: return GetFunctionType(functionCall); @@ -341,9 +341,9 @@ _ when TypeHelper.TryRemoveNullability(declaredType) is TypeSymbol nonNullable private TypeSymbol GetModifiedInteger(IntegerType declaredInteger, DecorableSyntax syntax, TypeSymbolValidationFlags validationFlags) { - var minValueDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, typeManager.GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMinValuePropertyName); + var minValueDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMinValuePropertyName); var minValue = GetSingleIntDecoratorArgument(minValueDecorator) ?? declaredInteger.MinValue; - var maxValueDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, typeManager.GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMaxValuePropertyName); + var maxValueDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMaxValuePropertyName); var maxValue = GetSingleIntDecoratorArgument(maxValueDecorator) ?? declaredInteger.MaxValue; if (minValue.HasValue && maxValue.HasValue && minValue.Value > maxValue.Value) @@ -372,12 +372,12 @@ private TypeSymbol GetModifiedInteger(IntegerType declaredInteger, DecorableSynt } private long? GetSingleIntDecoratorArgument(DecoratorSyntax? syntax) - => syntax?.Arguments.Count() == 1 && typeManager.GetTypeInfo(syntax.Arguments.Single()) is IntegerLiteralType integerLiteral + => syntax?.Arguments.Count() == 1 && model.TypeManager.GetTypeInfo(syntax.Arguments.Single()) is IntegerLiteralType integerLiteral ? integerLiteral.Value : null; private string? GetSingleStringDecoratorArgument(DecoratorSyntax? syntax) - => syntax?.Arguments.Count() == 1 && typeManager.GetTypeInfo(syntax.Arguments.Single()) is StringLiteralType stringLiteral + => syntax?.Arguments.Count() == 1 && model.TypeManager.GetTypeInfo(syntax.Arguments.Single()) is StringLiteralType stringLiteral ? stringLiteral.RawStringValue : null; @@ -403,9 +403,9 @@ private TypeSymbol GetModifiedString(StringType declaredString, DecorableSyntax private bool GetLengthModifiers(DecorableSyntax syntax, long? defaultMinLength, long? defaultMaxLength, out long? minLength, out long? maxLength, [NotNullWhen(false)] out ErrorType? error) { - var minLengthDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, typeManager.GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMinLengthPropertyName); + var minLengthDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMinLengthPropertyName); minLength = GetSingleIntDecoratorArgument(minLengthDecorator) ?? defaultMinLength; - var maxLengthDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, typeManager.GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMaxLengthPropertyName); + var maxLengthDecorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterMaxLengthPropertyName); maxLength = GetSingleIntDecoratorArgument(maxLengthDecorator) ?? defaultMaxLength; if (minLength.HasValue && maxLength.HasValue && minLength.Value > maxLength.Value) @@ -543,7 +543,7 @@ private static bool IsExtensibilityType(ResourceType resourceType) private TypeSymbol? GetOutputValueType(SyntaxBase syntax) => binder.GetParent(syntax) switch { - OutputDeclarationSyntax outputDeclaration => typeManager.GetTypeInfo(outputDeclaration.Value), + OutputDeclarationSyntax outputDeclaration => model.TypeManager.GetTypeInfo(outputDeclaration.Value), ParenthesizedExpressionSyntax parenthesized => GetOutputValueType(parenthesized), _ => null, }; @@ -642,7 +642,7 @@ private TypeSymbol GetObjectTypeType(ObjectTypeSyntax syntax) } propertyNamesEncountered.Add(propertyName); - properties.Add(new(propertyName, propertyType, TypePropertyFlags.Required, DescriptionHelper.TryGetFromDecorator(binder, typeManager, prop))); + properties.Add(new(propertyName, propertyType, TypePropertyFlags.Required, model.Facts.Description.Get(prop))); nameBuilder.AppendProperty(propertyName, GetPropertyTypeName(prop.Value, propertyType)); } else @@ -695,10 +695,10 @@ private static string GetPropertyTypeName(SyntaxBase typeSyntax, ITypeReference } private bool HasSecureDecorator(DecorableSyntax syntax) - => SemanticModelHelper.TryGetDecoratorInNamespace(binder, typeManager.GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterSecurePropertyName) is not null; + => SemanticModelHelper.TryGetDecoratorInNamespace(model, syntax, SystemNamespaceType.BuiltInName, LanguageConstants.ParameterSecurePropertyName) is not null; private DecoratorSyntax? TryGetSystemDecorator(DecorableSyntax syntax, string decoratorName) - => SemanticModelHelper.TryGetDecoratorInNamespace(binder, typeManager.GetDeclaredType, syntax, SystemNamespaceType.BuiltInName, decoratorName); + => SemanticModelHelper.TryGetDecoratorInNamespace(model, syntax, SystemNamespaceType.BuiltInName, decoratorName); private TupleType GetTupleTypeType(TupleTypeSyntax syntax) { @@ -731,7 +731,7 @@ private TypeSymbol ConvertTypeExpressionToType(StringTypeLiteralSyntax syntax) } else { - if (typeManager.GetTypeInfo(syntax.Expressions[i / 2]) is StringLiteralType literalSegment) + if (model.TypeManager.GetTypeInfo(syntax.Expressions[i / 2]) is StringLiteralType literalSegment) { literalText.Append(literalSegment.RawStringValue); } @@ -1218,7 +1218,7 @@ private DeclaredTypeAssignment GetTestType(TestDeclarationSyntax syntax) return this.GetDeclaredTypeAssignment(innerModuleBody); case VariableSymbol variableSymbol when IsCycleFree(variableSymbol): - var variableType = this.typeManager.GetTypeInfo(variableSymbol.DeclaringVariable.Value); + var variableType = model.TypeManager.GetTypeInfo(variableSymbol.DeclaringVariable.Value); return new DeclaredTypeAssignment(variableType, variableSymbol.DeclaringVariable); case ImportedVariableSymbol importedVariable: @@ -1323,7 +1323,7 @@ private DeclaredTypeAssignment GetTestType(TestDeclarationSyntax syntax) private DeclaredTypeAssignment? GetArrayAccessType(DeclaredTypeAssignment baseExpressionAssignment, ArrayAccessSyntax syntax) { - var indexAssignedType = this.typeManager.GetTypeInfo(syntax.IndexExpression); + var indexAssignedType = model.TypeManager.GetTypeInfo(syntax.IndexExpression); static TypeSymbol GetTypeAtIndex(TupleType baseType, IntegerLiteralType indexType, SyntaxBase indexSyntax) => indexType.Value switch { @@ -1502,7 +1502,7 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax private DeclaredTypeAssignment? GetFunctionType(FunctionCallSyntaxBase syntax) { - return new DeclaredTypeAssignment(this.typeManager.GetTypeInfo(syntax), declaringSyntax: null); + return new DeclaredTypeAssignment(model.TypeManager.GetTypeInfo(syntax), declaringSyntax: null); } private DeclaredTypeAssignment? GetFunctionArgumentType(FunctionArgumentSyntax syntax) @@ -1518,7 +1518,7 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax var argIndex = arguments.IndexOf(syntax); var declaredType = functionSymbol.GetDeclaredArgumentType( argIndex, - getAssignedArgumentType: i => typeManager.GetTypeInfo(parentFunction.Arguments[i])); + getAssignedArgumentType: i => model.TypeManager.GetTypeInfo(parentFunction.Arguments[i])); return new DeclaredTypeAssignment(declaredType, declaringSyntax: null); } @@ -1809,7 +1809,7 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax // `syntax.TryGetKeyText()` will only return a non-null value if the key is a bare identifier or a non-interpolated string // if it does return null, look at the *type* of the key and see if it's a string literal. If an interpolated key can be folded // to a literal type at compile time, this will likely already have been calculated and cached in the type manager - var propertyName = syntax.TryGetKeyText() ?? (typeManager.GetTypeInfo(syntax.Key) as StringLiteralType)?.RawStringValue; + var propertyName = syntax.TryGetKeyText() ?? (model.TypeManager.GetTypeInfo(syntax.Key) as StringLiteralType)?.RawStringValue; var parent = this.binder.GetParent(syntax); if (parent is not ObjectSyntax parentObject) { diff --git a/src/Bicep.Core/TypeSystem/FunctionFlags.cs b/src/Bicep.Core/TypeSystem/FunctionFlags.cs index 3ea78f82560..19778fbcaf9 100644 --- a/src/Bicep.Core/TypeSystem/FunctionFlags.cs +++ b/src/Bicep.Core/TypeSystem/FunctionFlags.cs @@ -87,6 +87,11 @@ public enum FunctionFlags /// IsArgumentValueIndependent = 1 << 15, + /// + /// The function can be to decorate a type property. + /// + TypePropertyDecorator = 1 << 16, + /// /// The function can be used as a resource or module decorator. /// @@ -95,21 +100,26 @@ public enum FunctionFlags /// /// The function can be used as a parameter or type decorator. /// - ParameterOrTypeDecorator = ParameterDecorator | TypeDecorator, + ParameterOrTypeDecorator = ParameterDecorator | TypeDecorator | TypePropertyDecorator, /// /// The function can be used as a parameter, output, or type decorator. /// - ParameterOutputOrTypeDecorator = ParameterDecorator | OutputDecorator | TypeDecorator, + ParameterOutputOrTypeDecorator = ParameterDecorator | OutputDecorator | TypeDecorator | TypePropertyDecorator, /// /// The function can be used as a type or variable decorator. /// - TypeVariableOrFunctionDecorator = TypeDecorator | VariableDecorator | FunctionDecorator, + TypeVariableOrFunctionDecorator = TypeDecorator | TypePropertyDecorator | VariableDecorator | FunctionDecorator, + + /// + /// Syntax that can be deprecated with the @deprecated() decorator. + /// + DeprecatableSyntaxDecorator = ParameterDecorator | OutputDecorator | TypeDecorator | VariableDecorator | FunctionDecorator, /// /// The function can be used as a decorator anywhere. /// - AnyDecorator = ParameterDecorator | VariableDecorator | FunctionDecorator | ResourceDecorator | ModuleDecorator | OutputDecorator | ExtensionDecorator | MetadataDecorator | TypeDecorator, + AnyDecorator = ParameterDecorator | VariableDecorator | FunctionDecorator | ResourceDecorator | ModuleDecorator | OutputDecorator | ExtensionDecorator | MetadataDecorator | TypeDecorator | TypePropertyDecorator, } } diff --git a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs index 995e6c0561e..186fb7159c9 100644 --- a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs +++ b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs @@ -1968,6 +1968,7 @@ public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) ModuleDeclarationSyntax _ => FunctionFlags.ModuleDecorator, ParameterDeclarationSyntax _ => FunctionFlags.ParameterDecorator, TypeDeclarationSyntax _ => FunctionFlags.TypeDecorator, + ObjectTypePropertySyntax _ => FunctionFlags.TypePropertyDecorator, VariableDeclarationSyntax _ => FunctionFlags.VariableDecorator, OutputDeclarationSyntax _ => FunctionFlags.OutputDecorator, _ => FunctionFlags.AnyDecorator, diff --git a/src/Bicep.Core/TypeSystem/TypeManager.cs b/src/Bicep.Core/TypeSystem/TypeManager.cs index 9e5759bcad6..e2e87de4526 100644 --- a/src/Bicep.Core/TypeSystem/TypeManager.cs +++ b/src/Bicep.Core/TypeSystem/TypeManager.cs @@ -19,7 +19,7 @@ public TypeManager(SemanticModel model, IBinder binder) // so we can't make an immutable copy here // (using the IReadOnlyDictionary to prevent accidental mutation) this.typeAssignmentVisitor = new(this, model); - this.declaredTypeManager = new(this, binder, model.Features); + this.declaredTypeManager = new(model, binder); } public TypeSymbol GetTypeInfo(SyntaxBase syntax) diff --git a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs index 28f87e911ee..d1c5aca4f32 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs @@ -1771,7 +1771,7 @@ private static CompletionItem CreateDeclaredTypeCompletion(SemanticModel model, .WithDetail(declaredType.Type.Name) .WithSortText(GetSortText(declaredType.Name, priority)); - if (DescriptionHelper.TryGetFromDecorator(model, declaredType.DeclaringType) is string documentation) + if (model.Facts.Description.Get(declaredType.DeclaringType) is string documentation) { builder = builder.WithDocumentation(documentation); } @@ -2190,7 +2190,7 @@ private static string FormatPropertyDocumentation(TypeProperty property) buffer.Append(MarkdownHelper.AppendNewline($"Type: `{declaredSymbol.Type}`")); } - var documentation = DescriptionHelper.TryGetFromDecorator(model, decorableSyntax); + var documentation = model.Facts.Description.Get(decorableSyntax); buffer.Append(MarkdownHelper.AppendNewline(documentation)); return buffer.ToString(); diff --git a/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs b/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs index b137872d2e2..091f685d27b 100644 --- a/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs @@ -61,17 +61,10 @@ public BicepHoverHandler( } private static string? TryGetDescription(SymbolResolutionResult result, DeclaredSymbol symbol) - { - if (symbol.DeclaringSyntax is DecorableSyntax decorableSyntax) - { - return DescriptionHelper.TryGetFromDecorator(result.Context.Compilation.GetEntrypointSemanticModel(), decorableSyntax); - } - - return null; - } + => symbol.Context.SemanticModel.Facts.Description.Get(symbol.DeclaringSyntax); private static string? TryGetDescription(SymbolResolutionResult result, WildcardImportSymbol symbol) - => DescriptionHelper.TryGetFromDecorator(result.Context.Compilation.GetEntrypointSemanticModel(), symbol.EnclosingDeclaration); + => symbol.Context.SemanticModel.Facts.Description.Get(symbol.EnclosingDeclaration); private static async Task GetMarkdown( HoverParams request, diff --git a/src/vscode-bicep/schemas/bicepconfig.schema.json b/src/vscode-bicep/schemas/bicepconfig.schema.json index e246e3df374..df7d57f8545 100644 --- a/src/vscode-bicep/schemas/bicepconfig.schema.json +++ b/src/vscode-bicep/schemas/bicepconfig.schema.json @@ -438,6 +438,16 @@ } ] }, + "no-deprecated-dependencies": { + "allOf": [ + { + "description": "Use of deprecated dependencies should be avoided. Defaults to 'Warning'. See https://aka.ms/bicep/linter/no-deprecated-dependencies" + }, + { + "$ref": "#/definitions/rule-def-level-warning" + } + ] + }, "no-hardcoded-env-urls": { "allOf": [ {