diff --git a/.gitattributes b/.gitattributes index ad657cb34a0..c62ff73ac37 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ -*.bicep -text -*.ts text eol=lf -*.cs text eol=lf -*.sh text eol=lf -/src/Bicep.Core.Samples/Files/**/Assets/**/*.txt -text - +*.bicep -text +*.bicepparam -text +*.ts text eol=lf +*.cs text eol=lf +*.sh text eol=lf +/src/Bicep.Core.Samples/Files/**/Assets/**/*.txt -text diff --git a/src/Bicep.Core.IntegrationTests/ParamsParserTests.cs b/src/Bicep.Core.IntegrationTests/ParamsParserTests.cs new file mode 100644 index 00000000000..a447ed25f1f --- /dev/null +++ b/src/Bicep.Core.IntegrationTests/ParamsParserTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.Parsing; +using Bicep.Core.Samples; +using Bicep.Core.UnitTests.Utils; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using System; + +namespace Bicep.Core.IntegrationTests +{ + [TestClass] + public class ParamsParserTests + { + [NotNull] + public TestContext? TestContext { get; set; } + + [DataTestMethod] + [DynamicData(nameof(GetParamData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] + [TestCategory(BaselineHelper.BaselineTestCategory)] + public void Params_Parser_should_produce_expected_syntax(DataSet dataSet) + { + if (dataSet.BicepParam is null) + { + throw new InvalidOperationException($"Expected {nameof(dataSet.BicepParam)} to be non-null"); + } + + var program = ParamsParserHelper.ParamsParse(dataSet.BicepParam); + var syntaxList = SyntaxCollectorVisitorHelper.SyntaxCollectorVisitor.Build(program); + var syntaxByParent = syntaxList.ToLookup(x => x.Parent); + + string getLoggingString(SyntaxCollectorVisitorHelper.SyntaxCollectorVisitor.SyntaxItem data) + { + // Build a visual graph with lines to help understand the syntax hierarchy + var graphPrefix = ""; + if (data.Depth > 0) + { + var lastSibling = syntaxByParent[data.Parent].Last(); + var isLast = data.Syntax == lastSibling.Syntax; + + graphPrefix = string.Concat(Enumerable.Repeat("| ", data.Depth - 1)); + graphPrefix += isLast switch { + true => "└─", + _ => "├─", + }; + } + + return data.Syntax switch { + Token token => $"{graphPrefix}Token({token.Type}) |{OutputHelper.EscapeWhitespace(token.Text)}|", + _ => $"{graphPrefix}{data.Syntax.GetType().Name}", + }; + } + + TextSpan getSpan(SyntaxCollectorVisitorHelper.SyntaxCollectorVisitor.SyntaxItem data) => data.Syntax.Span; + + var sourceTextWithDiags = DataSet.AddDiagsToParamSourceText(dataSet, syntaxList, getSpan, getLoggingString); + var resultsFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainParamSyntax), sourceTextWithDiags); + + sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + TestContext, + dataSet.ParamSyntax ?? throw new InvalidOperationException($"Expected {nameof(dataSet.ParamSyntax)} to be non-null."), + expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainParamSyntax), + actualLocation: resultsFile); + } + + private static IEnumerable GetParamData() + { + return DataSets.ParamDataSets.ToDynamicTestData(); + } + + } +} diff --git a/src/Bicep.Core.IntegrationTests/ParserTests.cs b/src/Bicep.Core.IntegrationTests/ParserTests.cs index f91b40b6fb5..523e4f111ae 100644 --- a/src/Bicep.Core.IntegrationTests/ParserTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParserTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; using Bicep.Core.Extensions; @@ -20,39 +19,6 @@ namespace Bicep.Core.IntegrationTests [TestClass] public class ParserTests { - private class SyntaxCollectorVisitor : SyntaxVisitor - { - public record SyntaxItem(SyntaxBase Syntax, SyntaxBase? Parent, int Depth); - - private readonly IList syntaxList = new List(); - private SyntaxBase? parent = null; - private int depth = 0; - - private SyntaxCollectorVisitor() - { - } - - public static ImmutableArray Build(ProgramSyntax syntax) - { - var visitor = new SyntaxCollectorVisitor(); - visitor.Visit(syntax); - - return visitor.syntaxList.ToImmutableArray(); - } - - protected override void VisitInternal(SyntaxBase syntax) - { - syntaxList.Add(new(Syntax: syntax, Parent: parent, Depth: depth)); - - var prevParent = parent; - parent = syntax; - depth++; - base.VisitInternal(syntax); - depth--; - parent = prevParent; - } - } - [NotNull] public TestContext? TestContext { get; set; } @@ -91,10 +57,10 @@ public void Oneliners_ShouldRoundTripSuccessfully(string contents) public void Parser_should_produce_expected_syntax(DataSet dataSet) { var program = ParserHelper.Parse(dataSet.Bicep); - var syntaxList = SyntaxCollectorVisitor.Build(program); + var syntaxList = SyntaxCollectorVisitorHelper.SyntaxCollectorVisitor.Build(program); var syntaxByParent = syntaxList.ToLookup(x => x.Parent); - string getLoggingString(SyntaxCollectorVisitor.SyntaxItem data) + string getLoggingString(SyntaxCollectorVisitorHelper.SyntaxCollectorVisitor.SyntaxItem data) { // Build a visual graph with lines to help understand the syntax hierarchy var graphPrefix = ""; @@ -116,7 +82,7 @@ string getLoggingString(SyntaxCollectorVisitor.SyntaxItem data) }; } - TextSpan getSpan(SyntaxCollectorVisitor.SyntaxItem data) => data.Syntax.Span; + TextSpan getSpan(SyntaxCollectorVisitorHelper.SyntaxCollectorVisitor.SyntaxItem data) => data.Syntax.Span; var sourceTextWithDiags = DataSet.AddDiagsToSourceText(dataSet, syntaxList, getSpan, getLoggingString); var resultsFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainSyntax), sourceTextWithDiags); diff --git a/src/Bicep.Core.IntegrationTests/SyntaxCollectorVisitorHelper.cs b/src/Bicep.Core.IntegrationTests/SyntaxCollectorVisitorHelper.cs new file mode 100644 index 00000000000..5e38f0b67bc --- /dev/null +++ b/src/Bicep.Core.IntegrationTests/SyntaxCollectorVisitorHelper.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.Immutable; +using Bicep.Core.Syntax; + +namespace Bicep.Core.IntegrationTests +{ + public class SyntaxCollectorVisitorHelper + { + public class SyntaxCollectorVisitor : SyntaxVisitor + { + public record SyntaxItem(SyntaxBase Syntax, SyntaxBase? Parent, int Depth); + + private readonly IList syntaxList = new List(); + private SyntaxBase? parent = null; + private int depth = 0; + + private SyntaxCollectorVisitor() + { + } + + public static ImmutableArray Build(ProgramSyntax syntax) + { + var visitor = new SyntaxCollectorVisitor(); + visitor.Visit(syntax); + + return visitor.syntaxList.ToImmutableArray(); + } + + protected override void VisitInternal(SyntaxBase syntax) + { + syntaxList.Add(new(Syntax: syntax, Parent: parent, Depth: depth)); + + var prevParent = parent; + parent = syntax; + depth++; + base.VisitInternal(syntax); + depth--; + parent = prevParent; + } + } + } +} diff --git a/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj b/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj index 8ce9be98864..8574a31088a 100644 --- a/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj +++ b/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Bicep.Core.Samples/DataSet.cs b/src/Bicep.Core.Samples/DataSet.cs index a168b17fb8a..2f1c7dd5c95 100644 --- a/src/Bicep.Core.Samples/DataSet.cs +++ b/src/Bicep.Core.Samples/DataSet.cs @@ -20,10 +20,12 @@ namespace Bicep.Core.Samples public class DataSet { public const string TestFileMain = "main.bicep"; + public const string TestFileMainParam = "main.bicepparam"; public const string TestFileMainDiagnostics = "main.diagnostics.bicep"; public const string TestFileMainTokens = "main.tokens.bicep"; public const string TestFileMainSymbols = "main.symbols.bicep"; public const string TestFileMainSyntax = "main.syntax.bicep"; + public const string TestFileMainParamSyntax = "main.syntax.bicepparam"; public const string TestFileMainFormatted = "main.formatted.bicep"; public const string TestFileMainCompiled = "main.json"; public const string TestFileMainCompiledWithSymbolicNames = "main.symbolicnames.json"; @@ -44,6 +46,8 @@ public record ExternalModuleMetadata(string Target); private readonly Lazy lazyBicep; + private readonly Lazy? lazyBicepParam; + private readonly Lazy lazyTokens; private readonly Lazy lazyDiagnostics; @@ -54,6 +58,8 @@ public record ExternalModuleMetadata(string Target); private readonly Lazy lazySyntax; + private readonly Lazy? lazyParamSyntax; + private readonly Lazy lazySymbols; private readonly Lazy lazyFormatted; @@ -69,12 +75,14 @@ public DataSet(string name) this.Name = name; this.lazyBicep = this.CreateRequired(TestFileMain); + this.lazyBicepParam = this.CreateOptional(TestFileMainParam); this.lazyTokens = this.CreateRequired(TestFileMainTokens); this.lazyDiagnostics = this.CreateRequired(TestFileMainDiagnostics); this.lazyCompiled = this.CreateIffValid(TestFileMainCompiled); this.lazyCompiledWithSymbolicNames = this.CreateIffValid(TestFileMainCompiledWithSymbolicNames); this.lazySymbols = this.CreateRequired(TestFileMainSymbols); this.lazySyntax = this.CreateRequired(TestFileMainSyntax); + this.lazyParamSyntax = this.CreateOptional(TestFileMainParamSyntax); this.lazyFormatted = this.CreateRequired(TestFileMainFormatted); this.lazyCompletions = new(() => ReadDataSetDictionary(GetStreamName(TestCompletionsPrefix)), LazyThreadSafetyMode.PublicationOnly); this.lazyModulesToPublish = new(() => ReadPublishData(GetStreamName(TestPublishPrefix)), LazyThreadSafetyMode.PublicationOnly); @@ -87,6 +95,8 @@ public DataSet(string name) public string Bicep => this.lazyBicep.Value; + public string? BicepParam => this.lazyBicepParam?.Value; + public string Tokens => this.lazyTokens.Value; public string Diagnostics => this.lazyDiagnostics.Value; @@ -99,6 +109,8 @@ public DataSet(string name) public string Syntax => this.lazySyntax.Value; + public string? ParamSyntax => this.lazyParamSyntax?.Value; + public string Formatted => this.lazyFormatted.Value; public ImmutableDictionary Completions => this.lazyCompletions.Value; @@ -119,6 +131,8 @@ public DataSet(string name) public bool IsStress => this.Name.Contains("Stress", StringComparison.Ordinal); + public bool HasParamFile => this.BicepParam is not null; + private Lazy CreateRequired(string fileName) { return new Lazy(() => this.ReadDataSetFile(fileName), LazyThreadSafetyMode.PublicationOnly); @@ -126,6 +140,8 @@ private Lazy CreateRequired(string fileName) private Lazy? CreateIffValid(string fileName) => this.IsValid ? this.CreateRequired(fileName) : null; + private Lazy? CreateOptional(string fileName) => ExistsFile(GetStreamName(fileName)) ? this.CreateRequired(fileName) : null; + public static string GetDisplayName(MethodInfo info, object[] data) => $"{info.Name}_{((DataSet)data[0]).Name}"; private string ReadDataSetFile(string fileName) => ReadFile(GetStreamName(fileName)); @@ -144,6 +160,13 @@ public static string ReadFile(string streamName) return reader.ReadToEnd(); } + public static bool ExistsFile(string streamName) + { + using Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(streamName); + + return stream is not null; + } + public static ImmutableDictionary ReadDataSetDictionary(string streamNamePrefix) { var matches = Assembly.GetExecutingAssembly() @@ -161,6 +184,13 @@ public static string ReadFile(string streamName) return builder.ToImmutable(); } + public static string AddDiagsToParamSourceText(DataSet dataSet, IEnumerable items, Func getSpanFunc, Func diagsFunc) + => OutputHelper.AddDiagsToSourceText(dataSet.BicepParam ?? throw new ArgumentException($"{nameof(dataSet.BicepParam)} is null."), dataSet.HasCrLfNewlines() ? "\r\n" : "\n", items, getSpanFunc, diagsFunc); + + public static string AddDiagsToParamSourceText(DataSet dataSet, IEnumerable items, Func diagsFunc) + where TPositionable : IPositionable + => OutputHelper.AddDiagsToSourceText(dataSet.BicepParam ?? throw new ArgumentException($"{nameof(dataSet.BicepParam)} is null."), dataSet.HasCrLfNewlines() ? "\r\n" : "\n", items, item => item.Span, diagsFunc); + public static string AddDiagsToSourceText(DataSet dataSet, IEnumerable items, Func getSpanFunc, Func diagsFunc) => OutputHelper.AddDiagsToSourceText(dataSet.Bicep, dataSet.HasCrLfNewlines() ? "\r\n" : "\n", items, getSpanFunc, diagsFunc); diff --git a/src/Bicep.Core.Samples/DataSets.cs b/src/Bicep.Core.Samples/DataSets.cs index d6864ac34a9..4cb996be206 100644 --- a/src/Bicep.Core.Samples/DataSets.cs +++ b/src/Bicep.Core.Samples/DataSets.cs @@ -104,6 +104,8 @@ public static class DataSets public static IEnumerable NonStressDataSets => AllDataSets.Where(ds => !ds.IsStress); + public static IEnumerable ParamDataSets => AllDataSets.Where(ds => ds.HasParamFile); + public static ImmutableDictionary Completions => DataSet.ReadDataSetDictionary($"{DataSet.Prefix}{DataSet.TestCompletionsPrefix}"); public static ImmutableDictionary Functions => DataSet.ReadDataSetDictionary($"{DataSet.Prefix}{DataSet.TestFunctionsPrefix}"); diff --git a/src/Bicep.Core.Samples/Files/Parameters_LF/main.bicepparam b/src/Bicep.Core.Samples/Files/Parameters_LF/main.bicepparam new file mode 100644 index 00000000000..f5eca6f905f --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Parameters_LF/main.bicepparam @@ -0,0 +1,68 @@ +/* +This is a +multiline comment! +*/ + +// This is a single line comment + +// using keyword for specifying a Bicep file +using './main.bicep/' + +// parameter assignment to literals +param myInt = 42 +param myStr = "hello world!!" +param myBool = true + +// parameter assignment to objects +param myObj = { + name: 'vm1' + location: 'westus' +} +param myComplexObj = { + enabled: true + name: 'complex object!' + priority: 3 + data: { + a: 'b' + c: [ + 'd' + 'e' + ] + } +} + +// parameter assignment to arrays +param myIntArr = [ + 1 + 2 + 3 + 4 + 5 +] +param myStrArr = [ + 'ant' + 'bear' + 'cat' + 'dog' +] +param myComplexArr = [ + 'eagle' + 21 + false + { + f: [ + 'g' + 'h' + ] + } +] +param myFunction = union({}, {}) +param myComplexArrWithFunction = [ + { + foo: resourceGroup() + } + true + [ + 42 + ] +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Parameters_LF/main.syntax.bicepparam b/src/Bicep.Core.Samples/Files/Parameters_LF/main.syntax.bicepparam new file mode 100644 index 00000000000..715062c69dd --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Parameters_LF/main.syntax.bicepparam @@ -0,0 +1,358 @@ +/* +//@[00:769) ProgramSyntax +This is a +multiline comment! +*/ +//@[02:004) ├─Token(NewLine) |\n\n| + +// This is a single line comment +//@[32:034) ├─Token(NewLine) |\n\n| + +// using keyword for specifying a Bicep file +//@[44:045) ├─Token(NewLine) |\n| +using './main.bicep/' +//@[00:021) ├─UsingDeclarationSyntax +//@[00:005) | ├─Token(Identifier) |using| +//@[06:021) | └─StringSyntax +//@[06:021) | | └─Token(StringComplete) |'./main.bicep/'| +//@[21:023) ├─Token(NewLine) |\n\n| + +// parameter assignment to literals +//@[35:036) ├─Token(NewLine) |\n| +param myInt = 42 +//@[00:016) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:011) | ├─IdentifierSyntax +//@[06:011) | | └─Token(Identifier) |myInt| +//@[12:013) | ├─Token(Assignment) |=| +//@[14:016) | └─IntegerLiteralSyntax +//@[14:016) | | └─Token(Integer) |42| +//@[16:017) ├─Token(NewLine) |\n| +param myStr = "hello world!!" +//@[00:029) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:011) | ├─IdentifierSyntax +//@[06:011) | | └─Token(Identifier) |myStr| +//@[12:013) | ├─Token(Assignment) |=| +//@[14:029) | └─SkippedTriviaSyntax +//@[14:015) | | ├─Token(Unrecognized) |"| +//@[15:020) | | ├─Token(Identifier) |hello| +//@[21:026) | | ├─Token(Identifier) |world| +//@[26:027) | | ├─Token(Exclamation) |!| +//@[27:028) | | ├─Token(Exclamation) |!| +//@[28:029) | | └─Token(Unrecognized) |"| +//@[29:030) ├─Token(NewLine) |\n| +param myBool = true +//@[00:019) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:012) | ├─IdentifierSyntax +//@[06:012) | | └─Token(Identifier) |myBool| +//@[13:014) | ├─Token(Assignment) |=| +//@[15:019) | └─BooleanLiteralSyntax +//@[15:019) | | └─Token(TrueKeyword) |true| +//@[19:021) ├─Token(NewLine) |\n\n| + +// parameter assignment to objects +//@[34:035) ├─Token(NewLine) |\n| +param myObj = { +//@[00:050) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:011) | ├─IdentifierSyntax +//@[06:011) | | └─Token(Identifier) |myObj| +//@[12:013) | ├─Token(Assignment) |=| +//@[14:050) | └─ObjectSyntax +//@[14:015) | | ├─Token(LeftBrace) |{| +//@[15:016) | | ├─Token(NewLine) |\n| + name: 'vm1' +//@[01:012) | | ├─ObjectPropertySyntax +//@[01:005) | | | ├─IdentifierSyntax +//@[01:005) | | | | └─Token(Identifier) |name| +//@[05:006) | | | ├─Token(Colon) |:| +//@[07:012) | | | └─StringSyntax +//@[07:012) | | | | └─Token(StringComplete) |'vm1'| +//@[12:013) | | ├─Token(NewLine) |\n| + location: 'westus' +//@[01:019) | | ├─ObjectPropertySyntax +//@[01:009) | | | ├─IdentifierSyntax +//@[01:009) | | | | └─Token(Identifier) |location| +//@[09:010) | | | ├─Token(Colon) |:| +//@[11:019) | | | └─StringSyntax +//@[11:019) | | | | └─Token(StringComplete) |'westus'| +//@[19:020) | | ├─Token(NewLine) |\n| +} +//@[00:001) | | └─Token(RightBrace) |}| +//@[01:002) ├─Token(NewLine) |\n| +param myComplexObj = { +//@[00:123) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:018) | ├─IdentifierSyntax +//@[06:018) | | └─Token(Identifier) |myComplexObj| +//@[19:020) | ├─Token(Assignment) |=| +//@[21:123) | └─ObjectSyntax +//@[21:022) | | ├─Token(LeftBrace) |{| +//@[22:023) | | ├─Token(NewLine) |\n| + enabled: true +//@[01:014) | | ├─ObjectPropertySyntax +//@[01:008) | | | ├─IdentifierSyntax +//@[01:008) | | | | └─Token(Identifier) |enabled| +//@[08:009) | | | ├─Token(Colon) |:| +//@[10:014) | | | └─BooleanLiteralSyntax +//@[10:014) | | | | └─Token(TrueKeyword) |true| +//@[14:015) | | ├─Token(NewLine) |\n| + name: 'complex object!' +//@[01:024) | | ├─ObjectPropertySyntax +//@[01:005) | | | ├─IdentifierSyntax +//@[01:005) | | | | └─Token(Identifier) |name| +//@[05:006) | | | ├─Token(Colon) |:| +//@[07:024) | | | └─StringSyntax +//@[07:024) | | | | └─Token(StringComplete) |'complex object!'| +//@[24:025) | | ├─Token(NewLine) |\n| + priority: 3 +//@[01:012) | | ├─ObjectPropertySyntax +//@[01:009) | | | ├─IdentifierSyntax +//@[01:009) | | | | └─Token(Identifier) |priority| +//@[09:010) | | | ├─Token(Colon) |:| +//@[11:012) | | | └─IntegerLiteralSyntax +//@[11:012) | | | | └─Token(Integer) |3| +//@[12:013) | | ├─Token(NewLine) |\n| + data: { +//@[01:045) | | ├─ObjectPropertySyntax +//@[01:005) | | | ├─IdentifierSyntax +//@[01:005) | | | | └─Token(Identifier) |data| +//@[05:006) | | | ├─Token(Colon) |:| +//@[07:045) | | | └─ObjectSyntax +//@[07:008) | | | | ├─Token(LeftBrace) |{| +//@[08:009) | | | | ├─Token(NewLine) |\n| + a: 'b' +//@[02:008) | | | | ├─ObjectPropertySyntax +//@[02:003) | | | | | ├─IdentifierSyntax +//@[02:003) | | | | | | └─Token(Identifier) |a| +//@[03:004) | | | | | ├─Token(Colon) |:| +//@[05:008) | | | | | └─StringSyntax +//@[05:008) | | | | | | └─Token(StringComplete) |'b'| +//@[08:009) | | | | ├─Token(NewLine) |\n| + c: [ +//@[02:024) | | | | ├─ObjectPropertySyntax +//@[02:003) | | | | | ├─IdentifierSyntax +//@[02:003) | | | | | | └─Token(Identifier) |c| +//@[03:004) | | | | | ├─Token(Colon) |:| +//@[05:024) | | | | | └─ArraySyntax +//@[05:006) | | | | | | ├─Token(LeftSquare) |[| +//@[06:007) | | | | | | ├─Token(NewLine) |\n| + 'd' +//@[03:006) | | | | | | ├─ArrayItemSyntax +//@[03:006) | | | | | | | └─StringSyntax +//@[03:006) | | | | | | | | └─Token(StringComplete) |'d'| +//@[06:007) | | | | | | ├─Token(NewLine) |\n| + 'e' +//@[03:006) | | | | | | ├─ArrayItemSyntax +//@[03:006) | | | | | | | └─StringSyntax +//@[03:006) | | | | | | | | └─Token(StringComplete) |'e'| +//@[06:007) | | | | | | ├─Token(NewLine) |\n| + ] +//@[02:003) | | | | | | └─Token(RightSquare) |]| +//@[03:004) | | | | ├─Token(NewLine) |\n| + } +//@[01:002) | | | | └─Token(RightBrace) |}| +//@[02:003) | | ├─Token(NewLine) |\n| +} +//@[00:001) | | └─Token(RightBrace) |}| +//@[01:003) ├─Token(NewLine) |\n\n| + +// parameter assignment to arrays +//@[33:034) ├─Token(NewLine) |\n| +param myIntArr = [ +//@[00:035) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:014) | ├─IdentifierSyntax +//@[06:014) | | └─Token(Identifier) |myIntArr| +//@[15:016) | ├─Token(Assignment) |=| +//@[17:035) | └─ArraySyntax +//@[17:018) | | ├─Token(LeftSquare) |[| +//@[18:019) | | ├─Token(NewLine) |\n| + 1 +//@[01:002) | | ├─ArrayItemSyntax +//@[01:002) | | | └─IntegerLiteralSyntax +//@[01:002) | | | | └─Token(Integer) |1| +//@[02:003) | | ├─Token(NewLine) |\n| + 2 +//@[01:002) | | ├─ArrayItemSyntax +//@[01:002) | | | └─IntegerLiteralSyntax +//@[01:002) | | | | └─Token(Integer) |2| +//@[02:003) | | ├─Token(NewLine) |\n| + 3 +//@[01:002) | | ├─ArrayItemSyntax +//@[01:002) | | | └─IntegerLiteralSyntax +//@[01:002) | | | | └─Token(Integer) |3| +//@[02:003) | | ├─Token(NewLine) |\n| + 4 +//@[01:002) | | ├─ArrayItemSyntax +//@[01:002) | | | └─IntegerLiteralSyntax +//@[01:002) | | | | └─Token(Integer) |4| +//@[02:003) | | ├─Token(NewLine) |\n| + 5 +//@[01:002) | | ├─ArrayItemSyntax +//@[01:002) | | | └─IntegerLiteralSyntax +//@[01:002) | | | | └─Token(Integer) |5| +//@[02:003) | | ├─Token(NewLine) |\n| +] +//@[00:001) | | └─Token(RightSquare) |]| +//@[01:002) ├─Token(NewLine) |\n| +param myStrArr = [ +//@[00:049) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:014) | ├─IdentifierSyntax +//@[06:014) | | └─Token(Identifier) |myStrArr| +//@[15:016) | ├─Token(Assignment) |=| +//@[17:049) | └─ArraySyntax +//@[17:018) | | ├─Token(LeftSquare) |[| +//@[18:019) | | ├─Token(NewLine) |\n| + 'ant' +//@[01:006) | | ├─ArrayItemSyntax +//@[01:006) | | | └─StringSyntax +//@[01:006) | | | | └─Token(StringComplete) |'ant'| +//@[06:007) | | ├─Token(NewLine) |\n| + 'bear' +//@[01:007) | | ├─ArrayItemSyntax +//@[01:007) | | | └─StringSyntax +//@[01:007) | | | | └─Token(StringComplete) |'bear'| +//@[07:008) | | ├─Token(NewLine) |\n| + 'cat' +//@[01:006) | | ├─ArrayItemSyntax +//@[01:006) | | | └─StringSyntax +//@[01:006) | | | | └─Token(StringComplete) |'cat'| +//@[06:007) | | ├─Token(NewLine) |\n| + 'dog' +//@[01:006) | | ├─ArrayItemSyntax +//@[01:006) | | | └─StringSyntax +//@[01:006) | | | | └─Token(StringComplete) |'dog'| +//@[06:007) | | ├─Token(NewLine) |\n| +] +//@[00:001) | | └─Token(RightSquare) |]| +//@[01:002) ├─Token(NewLine) |\n| +param myComplexArr = [ +//@[00:075) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:018) | ├─IdentifierSyntax +//@[06:018) | | └─Token(Identifier) |myComplexArr| +//@[19:020) | ├─Token(Assignment) |=| +//@[21:075) | └─ArraySyntax +//@[21:022) | | ├─Token(LeftSquare) |[| +//@[22:023) | | ├─Token(NewLine) |\n| + 'eagle' +//@[01:008) | | ├─ArrayItemSyntax +//@[01:008) | | | └─StringSyntax +//@[01:008) | | | | └─Token(StringComplete) |'eagle'| +//@[08:009) | | ├─Token(NewLine) |\n| + 21 +//@[01:003) | | ├─ArrayItemSyntax +//@[01:003) | | | └─IntegerLiteralSyntax +//@[01:003) | | | | └─Token(Integer) |21| +//@[03:004) | | ├─Token(NewLine) |\n| + false +//@[01:006) | | ├─ArrayItemSyntax +//@[01:006) | | | └─BooleanLiteralSyntax +//@[01:006) | | | | └─Token(FalseKeyword) |false| +//@[06:007) | | ├─Token(NewLine) |\n| + { +//@[01:030) | | ├─ArrayItemSyntax +//@[01:030) | | | └─ObjectSyntax +//@[01:002) | | | | ├─Token(LeftBrace) |{| +//@[02:003) | | | | ├─Token(NewLine) |\n| + f: [ +//@[02:024) | | | | ├─ObjectPropertySyntax +//@[02:003) | | | | | ├─IdentifierSyntax +//@[02:003) | | | | | | └─Token(Identifier) |f| +//@[03:004) | | | | | ├─Token(Colon) |:| +//@[05:024) | | | | | └─ArraySyntax +//@[05:006) | | | | | | ├─Token(LeftSquare) |[| +//@[06:007) | | | | | | ├─Token(NewLine) |\n| + 'g' +//@[03:006) | | | | | | ├─ArrayItemSyntax +//@[03:006) | | | | | | | └─StringSyntax +//@[03:006) | | | | | | | | └─Token(StringComplete) |'g'| +//@[06:007) | | | | | | ├─Token(NewLine) |\n| + 'h' +//@[03:006) | | | | | | ├─ArrayItemSyntax +//@[03:006) | | | | | | | └─StringSyntax +//@[03:006) | | | | | | | | └─Token(StringComplete) |'h'| +//@[06:007) | | | | | | ├─Token(NewLine) |\n| + ] +//@[02:003) | | | | | | └─Token(RightSquare) |]| +//@[03:004) | | | | ├─Token(NewLine) |\n| + } +//@[01:002) | | | | └─Token(RightBrace) |}| +//@[02:003) | | ├─Token(NewLine) |\n| +] +//@[00:001) | | └─Token(RightSquare) |]| +//@[01:002) ├─Token(NewLine) |\n| +param myFunction = union({}, {}) +//@[00:032) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:016) | ├─IdentifierSyntax +//@[06:016) | | └─Token(Identifier) |myFunction| +//@[17:018) | ├─Token(Assignment) |=| +//@[19:032) | └─FunctionCallSyntax +//@[19:024) | | ├─IdentifierSyntax +//@[19:024) | | | └─Token(Identifier) |union| +//@[24:025) | | ├─Token(LeftParen) |(| +//@[25:027) | | ├─FunctionArgumentSyntax +//@[25:027) | | | └─ObjectSyntax +//@[25:026) | | | | ├─Token(LeftBrace) |{| +//@[26:027) | | | | └─Token(RightBrace) |}| +//@[27:028) | | ├─Token(Comma) |,| +//@[29:031) | | ├─FunctionArgumentSyntax +//@[29:031) | | | └─ObjectSyntax +//@[29:030) | | | | ├─Token(LeftBrace) |{| +//@[30:031) | | | | └─Token(RightBrace) |}| +//@[31:032) | | └─Token(RightParen) |)| +//@[32:033) ├─Token(NewLine) |\n| +param myComplexArrWithFunction = [ +//@[00:087) ├─ParameterAssignmentSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:030) | ├─IdentifierSyntax +//@[06:030) | | └─Token(Identifier) |myComplexArrWithFunction| +//@[31:032) | ├─Token(Assignment) |=| +//@[33:087) | └─ArraySyntax +//@[33:034) | | ├─Token(LeftSquare) |[| +//@[34:035) | | ├─Token(NewLine) |\n| + { +//@[01:028) | | ├─ArrayItemSyntax +//@[01:028) | | | └─ObjectSyntax +//@[01:002) | | | | ├─Token(LeftBrace) |{| +//@[02:003) | | | | ├─Token(NewLine) |\n| + foo: resourceGroup() +//@[02:022) | | | | ├─ObjectPropertySyntax +//@[02:005) | | | | | ├─IdentifierSyntax +//@[02:005) | | | | | | └─Token(Identifier) |foo| +//@[05:006) | | | | | ├─Token(Colon) |:| +//@[07:022) | | | | | └─FunctionCallSyntax +//@[07:020) | | | | | | ├─IdentifierSyntax +//@[07:020) | | | | | | | └─Token(Identifier) |resourceGroup| +//@[20:021) | | | | | | ├─Token(LeftParen) |(| +//@[21:022) | | | | | | └─Token(RightParen) |)| +//@[22:023) | | | | ├─Token(NewLine) |\n| + } +//@[01:002) | | | | └─Token(RightBrace) |}| +//@[02:003) | | ├─Token(NewLine) |\n| + true +//@[01:005) | | ├─ArrayItemSyntax +//@[01:005) | | | └─BooleanLiteralSyntax +//@[01:005) | | | | └─Token(TrueKeyword) |true| +//@[05:006) | | ├─Token(NewLine) |\n| + [ +//@[01:015) | | ├─ArrayItemSyntax +//@[01:015) | | | └─ArraySyntax +//@[01:002) | | | | ├─Token(LeftSquare) |[| +//@[02:003) | | | | ├─Token(NewLine) |\n| + 42 +//@[05:007) | | | | ├─ArrayItemSyntax +//@[05:007) | | | | | └─IntegerLiteralSyntax +//@[05:007) | | | | | | └─Token(Integer) |42| +//@[07:008) | | | | ├─Token(NewLine) |\n| + ] +//@[03:004) | | | | └─Token(RightSquare) |]| +//@[04:005) | | ├─Token(NewLine) |\n| +] +//@[00:001) | | └─Token(RightSquare) |]| +//@[01:001) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.UnitTests/Parsing/ParamsParserTests.cs b/src/Bicep.Core.UnitTests/Parsing/ParamsParserTests.cs index 9a533b99ec6..d0c58062e67 100644 --- a/src/Bicep.Core.UnitTests/Parsing/ParamsParserTests.cs +++ b/src/Bicep.Core.UnitTests/Parsing/ParamsParserTests.cs @@ -35,7 +35,25 @@ public void TestParsingParameterAssignment(String text) } [DataTestMethod] - [DataRow("using './bicep.main' \n")] + [DataRow("param myobj = {\nname : 'vm1'\nlocation : 'westus'\n} \n")] + public void TestParameterObjectAssignment(String text) + { + var programSyntax = ParamsParserHelper.ParamsParse(text); + + programSyntax.Children.OfType().Should().HaveCount(1); + } + + [DataTestMethod] + [DataRow("param myarr = [ 1\n2\n3\n4\n5 ] \n")] + public void TestParameterArrayAssignment(String text) + { + var programSyntax = ParamsParserHelper.ParamsParse(text); + + programSyntax.Children.OfType().Should().HaveCount(1); + } + + [DataTestMethod] + [DataRow("using './main.bicep' \n")] public void TestParsingUsingKeyword(String text) { var programSyntax = ParamsParserHelper.ParamsParse(text); @@ -43,7 +61,6 @@ public void TestParsingUsingKeyword(String text) programSyntax.Children.OfType().Should().HaveCount(1); } - private static SyntaxBase RunExpressionTest(string text, string expected, Type expectedRootType) { SyntaxBase expression = ParserHelper.ParseExpression(text); diff --git a/src/Bicep.Core/Syntax/SyntaxVisitor.cs b/src/Bicep.Core/Syntax/SyntaxVisitor.cs index 7a70de5030b..4a208b8482d 100644 --- a/src/Bicep.Core/Syntax/SyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxVisitor.cs @@ -332,12 +332,18 @@ public virtual void VisitImportDeclarationSyntax(ImportDeclarationSyntax syntax) public virtual void VisitParameterAssignmentSyntax(ParameterAssignmentSyntax syntax) { - //TODO: unsure what implementation would mean here + this.VisitNodes(syntax.LeadingNodes); + this.Visit(syntax.Keyword); + this.Visit(syntax.Name); + this.Visit(syntax.Assignment); + this.Visit(syntax.Value); } public virtual void VisitUsingDeclarationSyntax(UsingDeclarationSyntax syntax) { - //TODO: unsure what implementation would mean here + this.VisitNodes(syntax.LeadingNodes); + this.Visit(syntax.Keyword); + this.Visit(syntax.Path); } protected void VisitTokens(IEnumerable tokens) diff --git a/src/Bicep.LangServer.IntegrationTests/SemanticTokenTests.cs b/src/Bicep.LangServer.IntegrationTests/SemanticTokenTests.cs index 8081edf23aa..59b4d4b63ab 100644 --- a/src/Bicep.LangServer.IntegrationTests/SemanticTokenTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/SemanticTokenTests.cs @@ -108,6 +108,7 @@ public async Task Correct_semantic_tokens_are_returned_for_params_file(string te } } + private static IEnumerable CalculateSemanticTokenInfos(IReadOnlyList lineStarts, IEnumerable semanticTokenData, MultiFileLanguageServerHelper helper) { var legend = helper.Client.ServerSettings.Capabilities.SemanticTokensProvider!.Legend; @@ -147,11 +148,10 @@ private static IEnumerable GetData() private static IEnumerable GetParamsData() { - yield return new object[] { "using './bicep.main' \n", new TextSpan[] { new TextSpan(0, 5) }, new SemanticTokenType[] {SemanticTokenType.Keyword} }; - yield return new object[] { "param myint = 12 \n", new TextSpan[] { new TextSpan(0, 5), new TextSpan(6, 5)}, new SemanticTokenType[] {SemanticTokenType.Keyword, SemanticTokenType.Variable}}; + //yield return new object[] { "using './bicep.main' \n", new TextSpan[] { new TextSpan(0, 5), new TextSpan(6, 14) }, new SemanticTokenType[] {SemanticTokenType.Keyword, SemanticTokenType.String} }; + //yield return new object[] { "param myint = 12 \n", new TextSpan[] { new TextSpan(0, 5), new TextSpan(6, 5), new TextSpan(14, 2)}, new SemanticTokenType[] {SemanticTokenType.Keyword, SemanticTokenType.Variable, SemanticTokenType.Number}}; yield return new object[] { "using './bicep.main' \n param myint = 12 \n param mystr = 'test'", - new TextSpan[] { new TextSpan(0, 5), new TextSpan(23, 5), new TextSpan(29, 5), new TextSpan(42, 5), new TextSpan(48, 5)}, - new SemanticTokenType[] {SemanticTokenType.Keyword, SemanticTokenType.Keyword, SemanticTokenType.Variable, SemanticTokenType.Keyword, SemanticTokenType.Variable}}; - } + new TextSpan[] { new TextSpan(0, 5), new TextSpan(6, 14), new TextSpan(23, 5), new TextSpan(29, 5), new TextSpan(37, 2), new TextSpan(42, 5), new TextSpan(48, 5), new TextSpan(56, 6)}, + new SemanticTokenType[] {SemanticTokenType.Keyword, SemanticTokenType.String, SemanticTokenType.Keyword, SemanticTokenType.Variable, SemanticTokenType.Number, SemanticTokenType.Keyword, SemanticTokenType.Variable, SemanticTokenType.String}}; } } }