private LanguageExpression InlineVariablesRecursive(LanguageExpression original)
        {
            if (original is not FunctionExpression functionExpression)
            {
                return(original);
            }

            if (functionExpression.Function == "variables" && functionExpression.Parameters.Length == 1 && functionExpression.Parameters[0] is JTokenExpression variableNameExpression)
            {
                var variableVal = template["variables"]?[variableNameExpression.Value.ToString()];

                if (variableVal == null)
                {
                    throw new ArgumentException($"Unable to resolve variable {variableNameExpression.Value}");
                }

                if (variableVal.Type == JTokenType.String && variableVal.ToObject <string>() is string stringValue)
                {
                    var variableExpression = ExpressionHelpers.ParseExpression(stringValue);

                    return(InlineVariablesRecursive(variableExpression));
                }
            }

            var inlinedParameters = functionExpression.Parameters.Select(p => InlineVariablesRecursive(p));

            return(new FunctionExpression(
                       functionExpression.Function,
                       inlinedParameters.ToArray(),
                       functionExpression.Properties));
        }
Exemple #2
0
        public void EmitLanguageExpression(SyntaxBase syntax)
        {
            var symbol = context.SemanticModel.GetSymbolInfo(syntax);

            if (symbol is VariableSymbol variableSymbol && context.VariablesToInline.Contains(variableSymbol))
            {
                EmitExpression(variableSymbol.Value);
                return;
            }

            LanguageExpression converted = converter.ConvertExpression(syntax);

            if (converted is JTokenExpression valueExpression && valueExpression.Value.Type == JTokenType.Integer)
            {
                // the converted expression is an integer literal
                JToken value = valueExpression.Value;

                // for integer literals the expression will look like "[42]" or "[-12]"
                // while it's still a valid template expression that works in ARM, it looks weird
                // and is also not recognized by the template language service in VS code
                // let's serialize it as a proper integer instead
                writer.WriteValue(value);

                return;
            }

            // strings literals and other expressions must be processed with the serializer to ensure correct conversion and escaping
            var serialized = ExpressionSerializer.SerializeExpression(converted);

            writer.WriteValue(serialized);
        }
Exemple #3
0
        private LanguageExpression ConvertString(StringSyntax syntax)
        {
            if (syntax.TryGetLiteralValue() is string literalStringValue)
            {
                // no need to build a format string
                return(new JTokenExpression(literalStringValue));;
            }

            if (syntax.Expressions.Length == 1)
            {
                const string emptyStringOpen  = LanguageConstants.StringDelimiter + LanguageConstants.StringHoleOpen;  // '${
                const string emptyStringClose = LanguageConstants.StringHoleClose + LanguageConstants.StringDelimiter; // }'

                // Special-case interpolation of format '${myValue}' because it's a common pattern for userAssignedIdentities.
                // There's no need for a 'format' function because we just have a single expression with no outer formatting.
                if (syntax.StringTokens[0].Text == emptyStringOpen && syntax.StringTokens[1].Text == emptyStringClose)
                {
                    return(ConvertExpression(syntax.Expressions[0]));
                }
            }

            var formatArgs = new LanguageExpression[syntax.Expressions.Length + 1];

            var formatString = StringFormatConverter.BuildFormatString(syntax);

            formatArgs[0] = new JTokenExpression(formatString);

            for (var i = 0; i < syntax.Expressions.Length; i++)
            {
                formatArgs[i + 1] = ConvertExpression(syntax.Expressions[i]);
            }

            return(CreateFunction("format", formatArgs));
        }
        private LanguageExpression ConvertBinary(BinaryOperationSyntax syntax)
        {
            LanguageExpression operand1 = ConvertExpression(syntax.LeftExpression);
            LanguageExpression operand2 = ConvertExpression(syntax.RightExpression);

            return(syntax.Operator switch
            {
                BinaryOperator.LogicalOr => CreateFunction("or", operand1, operand2),
                BinaryOperator.LogicalAnd => CreateFunction("and", operand1, operand2),
                BinaryOperator.Equals => CreateFunction("equals", operand1, operand2),
                BinaryOperator.NotEquals => CreateFunction("not",
                                                           CreateFunction("equals", operand1, operand2)),
                BinaryOperator.EqualsInsensitive => CreateFunction("equals",
                                                                   CreateFunction("toLower", operand1),
                                                                   CreateFunction("toLower", operand2)),
                BinaryOperator.NotEqualsInsensitive => CreateFunction("not",
                                                                      CreateFunction("equals",
                                                                                     CreateFunction("toLower", operand1),
                                                                                     CreateFunction("toLower", operand2))),
                BinaryOperator.LessThan => CreateFunction("less", operand1, operand2),
                BinaryOperator.LessThanOrEqual => CreateFunction("lessOrEquals", operand1, operand2),
                BinaryOperator.GreaterThan => CreateFunction("greater", operand1, operand2),
                BinaryOperator.GreaterThanOrEqual => CreateFunction("greaterOrEquals", operand1, operand2),
                BinaryOperator.Add => CreateFunction("add", operand1, operand2),
                BinaryOperator.Subtract => CreateFunction("sub", operand1, operand2),
                BinaryOperator.Multiply => CreateFunction("mul", operand1, operand2),
                BinaryOperator.Divide => CreateFunction("div", operand1, operand2),
                BinaryOperator.Modulo => CreateFunction("mod", operand1, operand2),
                BinaryOperator.Coalesce => CreateFunction("coalesce", operand1, operand2),
                _ => throw new NotImplementedException($"Cannot emit unexpected binary operator '{syntax.Operator}'."),
            });
Exemple #5
0
 public static LanguageExpression GenerateResourceGroupScope(LanguageExpression subscriptionId, LanguageExpression resourceGroup)
 => new FunctionExpression("format", new LanguageExpression[]
 {
     new JTokenExpression("/subscriptions/{0}/resourceGroups/{1}"),
     subscriptionId,
     resourceGroup,
 }, new LanguageExpression[0]);
Exemple #6
0
        /// <summary>
        /// Writes the serialized expression to the buffer.
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="expression">The expression</param>
        /// <param name="prefix">The optional prefix</param>
        /// <param name="suffix">The optional suffix</param>
        private static void WriteExpressionToBuffer(StringBuilder buffer, LanguageExpression expression, string prefix = null, string suffix = null)
        {
            ExpressionSerializer.ValidateExpression(expression: expression);

            if (prefix != null)
            {
                buffer.Append(value: prefix);
            }

            switch (expression)
            {
            case FunctionExpression functionExpression:
                ExpressionSerializer.WriteFunctionExpressionToBuffer(buffer: buffer, functionExpression: functionExpression);
                break;

            case JTokenExpression valueExpression:
                ExpressionSerializer.WriteValueExpressionToBuffer(buffer: buffer, valueExpression: valueExpression);
                break;

            default:
                throw new InvalidOperationException($"Unexpected expression type '{expression.GetType().Name}'");
            }

            if (suffix != null)
            {
                buffer.Append(value: suffix);
            }
        }
Exemple #7
0
 /// <summary>
 /// Validates that the specified expression is not null. Does not perform recursive validation.
 /// </summary>
 /// <param name="expression">The expression</param>
 private static void ValidateExpression(LanguageExpression expression)
 {
     if (expression == null)
     {
         throw new ArgumentNullException(nameof(expression));
     }
 }
Exemple #8
0
        private LanguageExpression ConvertUnary(UnaryOperationSyntax syntax)
        {
            LanguageExpression convertedOperand = ConvertExpression(syntax.Expression);

            switch (syntax.Operator)
            {
            case UnaryOperator.Not:
                return(CreateFunction("not", convertedOperand));

            case UnaryOperator.Minus:
                if (convertedOperand is JTokenExpression literal && literal.Value.Type == JTokenType.Integer)
                {
                    // invert the integer literal
                    int literalValue = literal.Value.Value <int>();
                    return(new JTokenExpression(-literalValue));
                }

                return(CreateFunction(
                           "sub",
                           new JTokenExpression(0),
                           convertedOperand));

            default:
                throw new NotImplementedException($"Cannot emit unexpected unary operator '{syntax.Operator}.");
            }
        }
        public string?TryRequestResourceName(string typeString, LanguageExpression nameExpression)
        {
            // it's valid to include a trailing slash, so we need to normalize it
            typeString = typeString.TrimEnd('/');

            var assignedResourceKey = GetResourceNameKey(typeString, nameExpression);
            var nameString          = GetNameRecursive(nameExpression);

            // try to get a shorter name first if possible
            // if we've got two resources of different types with the same name, we may be forced to qualify it
            var unqualifiedName = TryRequestName(NameType.Resource, nameString);

            if (unqualifiedName != null)
            {
                assignedResourceNames[assignedResourceKey] = unqualifiedName;
                return(unqualifiedName);
            }

            var qualifiedName = TryRequestName(NameType.Resource, $"{typeString}_{nameString}");

            if (qualifiedName != null)
            {
                assignedResourceNames[assignedResourceKey] = qualifiedName;
                return(qualifiedName);
            }

            return(null);
        }
        public string?TryLookupResourceName(string?typeString, LanguageExpression nameExpression)
        {
            if (typeString is null)
            {
                var nameString        = ExpressionsEngine.SerializeExpression(nameExpression);
                var resourceKeySuffix = EscapeIdentifier($"_{nameString}");

                var matchingResources = assignedResourceNames.Where(kvp => kvp.Key.EndsWith(resourceKeySuffix, StringComparison.OrdinalIgnoreCase));
                if (matchingResources.Count() == 1)
                {
                    // only return a value if we're sure about the match
                    return(matchingResources.First().Value);
                }

                return(null);
            }

            // it's valid to include a trailing slash, so we need to normalize it
            typeString = typeString.TrimEnd('/');

            var assignedResourceKey = GetResourceNameKey(typeString, nameExpression);

            if (!assignedResourceNames.TryGetValue(assignedResourceKey, out var name))
            {
                return(null);
            }

            return(name);
        }
Exemple #11
0
 public static string? TryGetLocalFilePathForTemplateLink(LanguageExpression templateLinkExpression)
 {
     LanguageExpression? relativePath = null;
     if (templateLinkExpression is FunctionExpression uriExpression && uriExpression.Function == "uri")
     {
         // it's common to format references to files using the uri function. the second param is the relative path (which should match the file system path)
         relativePath = uriExpression.Parameters[1];
     }
Exemple #12
0
 public static LanguageExpression GetManagementGroupScopeExpression(LanguageExpression managementGroupName)
 => new FunctionExpression(
     "tenantResourceId",
     new LanguageExpression[] {
     new JTokenExpression("Microsoft.Management/managementGroups"),
     managementGroupName,
 },
     Array.Empty <LanguageExpression>());
Exemple #13
0
        private void EmitPropertyInternal(LanguageExpression expressionKey, Action valueFunc)
        {
            var serializedName = ExpressionSerializer.SerializeExpression(expressionKey);

            writer.WritePropertyName(serializedName);

            valueFunc();
        }
Exemple #14
0
 private static LanguageExpression GetManagementGroupScopeExpression(LanguageExpression managementGroupName)
 => new FunctionExpression(
     "format",
     new LanguageExpression[] {
     new JTokenExpression("Microsoft.Management/managementGroups/{0}"),
     managementGroupName,
 },
     Array.Empty <LanguageExpression>());
        private string?TryLookupResource(LanguageExpression expression)
        {
            var normalizedForm = ExpressionHelpers.TryGetResourceNormalizedForm(expression);

            if (normalizedForm is null)
            {
                // try to look it up without the type string, using the expression as-is as the name
                // it's fairly common to refer to another resource by name only
                return(nameResolver.TryLookupResourceName(null, expression));
            }

            return(nameResolver.TryLookupResourceName(normalizedForm.Value.typeString, normalizedForm.Value.nameExpression));
        }
Exemple #16
0
        /// <summary>
        /// Writes the specified property expression to the buffer.
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="propertyExpression">The property expression</param>
        private static void WritePropertyExpressionToBuffer(StringBuilder buffer, LanguageExpression propertyExpression)
        {
            ExpressionSerializer.ValidateExpression(expression: propertyExpression);

            switch (propertyExpression)
            {
            case FunctionExpression functionExpression:
                // Note(majastrz): Functions in properties should be enclosed in brackets
                ExpressionSerializer.WriteExpressionToBuffer(buffer: buffer, expression: functionExpression, prefix: "[", suffix: "]");

                return;

            case JTokenExpression valueExpression:
                var value = ExpressionSerializer.GetValueFromValueExpression(valueExpression: valueExpression);

                switch (value.Type)
                {
                case JTokenType.String:
                    var valueStr = value.ToString();

                    if (ExpressionSerializer.IsIdentifier(value: valueStr))
                    {
                        // Note(majastrz): The property expression is an identifier. We can serialize it with a leading dot and without any enclosing quotes.
                        buffer.Append(value: '.');
                        buffer.Append(value: valueStr);
                    }
                    else
                    {
                        // Note(majastrz): The property expression has to be enclosed in brackets because it's not an identifier.
                        ExpressionSerializer.WriteExpressionToBuffer(buffer: buffer, expression: valueExpression, prefix: "[", suffix: "]");
                    }

                    return;

                case JTokenType.Integer:
                    // Note(majastrz): Indexes should be enclosed in brackets.
                    buffer.Append(value: '[');
                    buffer.Append(value: value);
                    buffer.Append(value: ']');

                    return;

                default:
                    // Note(majastrz): JTokenValue can only be created with string or int value.
                    throw new InvalidOperationException(message: $"JTokenExpression has a value of unexpected type '{value.Type}'.");
                }

            default:
                throw new InvalidOperationException($"Unexpected expression type '{propertyExpression.GetType().Name}'");
            }
        }
        private static string GetNameRecursive(LanguageExpression expression)
        {
            if (expression is JTokenExpression jTokenExpression)
            {
                return(jTokenExpression.Value.ToString());
            }

            if (expression is FunctionExpression functionExpression)
            {
                var subExpressions = functionExpression.Parameters.Concat(functionExpression.Properties);

                return(string.Join('_', subExpressions.Select(GetNameRecursive)));
            }

            return("_");
        }
        private static (bool hasChanges, LanguageExpression[] output) RewriteInternal(LanguageExpression[] input, Func <LanguageExpression, LanguageExpression> rewriteFunc)
        {
            var output = new LanguageExpression[input.Length];

            var hasChanges = false;

            for (var i = 0; i < input.Length; i++)
            {
                var(entryHasChanges, entryOutput) = RewriteInternal(input[i], rewriteFunc);

                hasChanges |= entryHasChanges;
                output[i]   = entryOutput;
            }

            return(hasChanges, output);
        }
        private static (bool hasChanges, LanguageExpression output) RewriteInternal(LanguageExpression input, Func <LanguageExpression, LanguageExpression> rewriteFunc)
        {
            switch (input)
            {
            case FunctionExpression function:
            {
                var hasChanges = false;

                var newParameters = function.Parameters;
                if (function.Parameters is not null)
                {
                    var(hasChangesInner, output) = RewriteInternal(function.Parameters, rewriteFunc);
                    hasChanges   |= hasChangesInner;
                    newParameters = output;
                }

                var newProperties = function.Properties;
                if (function.Properties is not null)
                {
                    var(hasChangesInner, output) = RewriteInternal(function.Properties, rewriteFunc);
                    hasChanges   |= hasChangesInner;
                    newProperties = output;
                }

                if (hasChanges)
                {
                    function = new FunctionExpression(function.Function, newParameters, newProperties);
                }

                var rewritten = rewriteFunc(function);
                hasChanges |= !object.ReferenceEquals(function, rewritten);

                return(hasChanges, rewritten);
            }

            case JTokenExpression jtoken:
            {
                var rewritten  = rewriteFunc(jtoken);
                var hasChanges = !object.ReferenceEquals(jtoken, rewritten);

                return(hasChanges, rewritten);
            }

            default:
                throw new NotImplementedException($"Unrecognized expression type {input.GetType()}");
            }
        }
Exemple #20
0
        /// <summary>
        /// Serializes a language expression into a string.
        /// </summary>
        /// <param name="expression">The expression</param>
        public string SerializeExpression(LanguageExpression expression)
        {
            var buffer = new StringBuilder();

            if (!ExpressionSerializer.TryWriteSingleStringToBuffer(buffer: buffer, expression: expression, settings: this.settings))
            {
                // Note(majastrz): The expression is not a single string expression or the single string serialization is disabled

                // Note(majastrz): Include outer brackets when serializing the expression if feature is enabled
                ExpressionSerializer.WriteExpressionToBuffer(
                    buffer: buffer,
                    expression: expression,
                    prefix: this.settings.IncludeOuterSquareBrackets ? "[" : null,
                    suffix: this.settings.IncludeOuterSquareBrackets ? "]" : null);
            }

            return(buffer.ToString());
        }
Exemple #21
0
        public void EmitLanguageExpression(SyntaxBase syntax)
        {
            var symbol = context.SemanticModel.GetSymbolInfo(syntax);

            if (symbol is VariableSymbol variableSymbol && context.VariablesToInline.Contains(variableSymbol))
            {
                EmitExpression(variableSymbol.Value);
                return;
            }

            if (syntax is FunctionCallSyntax functionCall &&
                symbol is FunctionSymbol functionSymbol &&
                string.Equals(functionSymbol.Name, "any", LanguageConstants.IdentifierComparison))
            {
                // the outermost function in the current syntax node is the "any" function
                // we should emit its argument directly
                // otherwise, they'd get wrapped in a json() template function call in the converted expression

                // we have checks for function parameter count mismatch, which should prevent an exception from being thrown
                EmitExpression(functionCall.Arguments.Single().Expression);
                return;
            }

            LanguageExpression converted = converter.ConvertExpression(syntax);

            if (converted is JTokenExpression valueExpression && valueExpression.Value.Type == JTokenType.Integer)
            {
                // the converted expression is an integer literal
                JToken value = valueExpression.Value;

                // for integer literals the expression will look like "[42]" or "[-12]"
                // while it's still a valid template expression that works in ARM, it looks weird
                // and is also not recognized by the template language service in VS code
                // let's serialize it as a proper integer instead
                writer.WriteValue(value);

                return;
            }

            // strings literals and other expressions must be processed with the serializer to ensure correct conversion and escaping
            var serialized = ExpressionSerializer.SerializeExpression(converted);

            writer.WriteValue(serialized);
        }
Exemple #22
0
        private FunctionExpression ConvertObject(ObjectSyntax syntax)
        {
            // need keys and values in one array of parameters
            var parameters = new LanguageExpression[syntax.Properties.Count() * 2];

            int index = 0;

            foreach (var propertySyntax in syntax.Properties)
            {
                parameters[index] = new JTokenExpression(propertySyntax.TryGetKeyText());
                index++;

                parameters[index] = ConvertExpression(propertySyntax.Value);
                index++;
            }

            // we are using the createObject() funciton as a proy for an object literal
            return(GetCreateObjectExpression(parameters));
        }
        private LanguageExpression ConvertString(StringSyntax syntax)
        {
            if (!syntax.IsInterpolated())
            {
                // no need to build a format string
                return(new JTokenExpression(syntax.GetLiteralValue()));;
            }

            var formatArgs = new LanguageExpression[syntax.Expressions.Length + 1];

            var formatString = StringFormatConverter.BuildFormatString(syntax);

            formatArgs[0] = new JTokenExpression(formatString);

            for (var i = 0; i < syntax.Expressions.Length; i++)
            {
                formatArgs[i + 1] = ConvertExpression(syntax.Expressions[i]);
            }

            return(new FunctionExpression("format", formatArgs, Array.Empty <LanguageExpression>()));
        }
Exemple #24
0
        /// <summary>
        /// If the expression is a single string JTokenExpression and serialization as string is enabled, it performs the serialization.
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="expression">The expression</param>
        /// <param name="settings">The settings</param>
        /// <returns>True if expression has been written out to the buffer or false otherwise.</returns>
        private static bool TryWriteSingleStringToBuffer(StringBuilder buffer, LanguageExpression expression, ExpressionSerializerSettings settings)
        {
            if (settings.SingleStringHandling == ExpressionSerializerSingleStringHandling.SerializeAsString && expression is JTokenExpression valueExpression)
            {
                var value = ExpressionSerializer.GetValueFromValueExpression(valueExpression: valueExpression);
                if (value.Type == JTokenType.String)
                {
                    var valueStr = value.ToString();

                    // Note(majastrz): Add escape bracket if needed.
                    if (valueStr.Length > 0 && valueStr[0] == '[')
                    {
                        buffer.Append(value: '[');
                    }

                    buffer.Append(value: valueStr);

                    return(true);
                }
            }

            // Note(majastrz): Returning false REQUIRES that buffer not be modified in any way.
            return(false);
        }
        private FunctionExpression ConvertObject(ObjectSyntax syntax)
        {
            // need keys and values in one array of parameters
            var parameters = new LanguageExpression[syntax.Properties.Count() * 2];

            int index = 0;

            foreach (var propertySyntax in syntax.Properties)
            {
                parameters[index] = propertySyntax.Key switch
                {
                    IdentifierSyntax identifier => new JTokenExpression(identifier.IdentifierName),
                    StringSyntax @string => ConvertString(@string),
                    _ => throw new NotImplementedException($"Encountered an unexpected type '{propertySyntax.Key.GetType().Name}' when generating object's property name.")
                };
                index++;

                parameters[index] = ConvertExpression(propertySyntax.Value);
                index++;
            }

            // we are using the createObject() function as a proxy for an object literal
            return(GetCreateObjectExpression(parameters));
        }
        private LanguageExpression InlineVariables(LanguageExpression original)
        {
            var inlined = InlineVariablesRecursive(original);

            return(ExpressionHelpers.FlattenStringOperations(inlined));
        }
 public ExpressionConverter AppendReplacement(LocalVariableSymbol symbol, LanguageExpression replacement) =>
Exemple #28
0
 public static LanguageExpression GenerateScopedResourceId(LanguageExpression scope, string fullyQualifiedType, IEnumerable <LanguageExpression> nameSegments)
 => CreateFunction(
     "extensionResourceId",
     new [] { scope, new JTokenExpression(fullyQualifiedType), }.Concat(nameSegments));
Exemple #29
0
        private LanguageExpression ConvertBinary(BinaryOperationSyntax syntax)
        {
            LanguageExpression operand1 = ConvertExpression(syntax.LeftExpression);
            LanguageExpression operand2 = ConvertExpression(syntax.RightExpression);

            switch (syntax.Operator)
            {
            case BinaryOperator.LogicalOr:
                return(CreateFunction("or", operand1, operand2));

            case BinaryOperator.LogicalAnd:
                return(CreateFunction("and", operand1, operand2));

            case BinaryOperator.Equals:
                return(CreateFunction("equals", operand1, operand2));

            case BinaryOperator.NotEquals:
                return(CreateFunction("not",
                                      CreateFunction("equals", operand1, operand2)));

            case BinaryOperator.EqualsInsensitive:
                return(CreateFunction("equals",
                                      CreateFunction("toLower", operand1),
                                      CreateFunction("toLower", operand2)));

            case BinaryOperator.NotEqualsInsensitive:
                return(CreateFunction("not",
                                      CreateFunction("equals",
                                                     CreateFunction("toLower", operand1),
                                                     CreateFunction("toLower", operand2))));

            case BinaryOperator.LessThan:
                return(CreateFunction("less", operand1, operand2));

            case BinaryOperator.LessThanOrEqual:
                return(CreateFunction("lessOrEquals", operand1, operand2));

            case BinaryOperator.GreaterThan:
                return(CreateFunction("greater", operand1, operand2));

            case BinaryOperator.GreaterThanOrEqual:
                return(CreateFunction("greaterOrEquals", operand1, operand2));

            case BinaryOperator.Add:
                return(CreateFunction("add", operand1, operand2));

            case BinaryOperator.Subtract:
                return(CreateFunction("sub", operand1, operand2));

            case BinaryOperator.Multiply:
                return(CreateFunction("mul", operand1, operand2));

            case BinaryOperator.Divide:
                return(CreateFunction("div", operand1, operand2));

            case BinaryOperator.Modulo:
                return(CreateFunction("mod", operand1, operand2));

            default:
                throw new NotImplementedException($"Cannot emit unexpected binary operator '{syntax.Operator}'.");
            }
        }
        private string GetResourceNameKey(string typeString, LanguageExpression nameExpression)
        {
            var nameString = ExpressionsEngine.SerializeExpression(nameExpression);

            return(EscapeIdentifier($"{typeString}_{nameString}"));
        }