Esempio n. 1
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));
        }
Esempio n. 2
0
        public static LanguageExpression FormatLocallyScopedResourceId(SemanticModel.SemanticModel semanticModel, string fullyQualifiedType, IEnumerable <LanguageExpression> nameSegments)
        {
            var initialArgs = new JTokenExpression(fullyQualifiedType).AsEnumerable();

            switch (semanticModel.TargetScope)
            {
            case ResourceScopeType.TenantScope:
                var tenantArgs = initialArgs.Concat(nameSegments);
                return(new FunctionExpression("tenantResourceId", tenantArgs.ToArray(), new LanguageExpression[0]));

            case ResourceScopeType.SubscriptionScope:
                var subscriptionArgs = initialArgs.Concat(nameSegments);
                return(new FunctionExpression("subscriptionResourceId", subscriptionArgs.ToArray(), new LanguageExpression[0]));

            case ResourceScopeType.ResourceGroupScope:
                var resourceGroupArgs = initialArgs.Concat(nameSegments);
                return(new FunctionExpression("resourceId", resourceGroupArgs.ToArray(), new LanguageExpression[0]));

            case ResourceScopeType.ManagementGroupScope:
                // We need to do things slightly differently for Management Groups, because there is no IL to output for "Give me a fully-qualified resource id at the current scope",
                // and we don't even have a mechanism for reliably getting the current scope (e.g. something like 'deployment().scope'). There are plans to add a managementGroupResourceId function,
                // but until we have it, we should generate unqualified resource Ids. There should not be a risk of collision, because we do not allow mixing of resource scopes in a single bicep file.
                return(ExpressionConverter.GenerateUnqualifiedResourceId(fullyQualifiedType, nameSegments));

            default:
                // this should have already been caught during compilation
                throw new InvalidOperationException($"Invalid target scope {semanticModel.TargetScope} for module");
            }
        }
Esempio n. 3
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));
        }
Esempio n. 4
0
        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>()));
        }
Esempio n. 5
0
        public static (string typeString, LanguageExpression nameExpression)? TryGetResourceNormalizedForm(LanguageExpression expression)
        {
            expression = FlattenStringOperations(expression);

            if (expression is not FunctionExpression functionExpression)
            {
                // could be a string literal (e.g. "My.RP/typeA/nameA"). May as well try and parse it with the below logic.
                functionExpression = Concat(expression);
            }

            if (functionExpression.NameEquals("concat"))
            {
                // this is a heuristic but appears to be quite common - e.g. to format a resourceId using concat('My.Rp/type', parameters(name)) rather than resourceId().
                // It doesn't really hurt to see if we can find a match for it.

                var segments = functionExpression.Parameters
                               .SelectMany(x => x is JTokenExpression jTokenExpression ? jTokenExpression.Value.ToString().Split('/').Where(y => y != "").Select(y => new JTokenExpression(y)) : x.AsEnumerable())
                               .ToArray();

                if (segments.Length < 3 || segments.Length % 2 != 1)
                {
                    return(null);
                }

                var types = segments.Take(1).Concat(segments.Skip(1).Where((x, i) => i % 2 == 0));
                var names = segments.Skip(1).Where((x, i) => i % 2 == 1);

                var typeBuilder = new StringBuilder();
                foreach (var type in types)
                {
                    if (typeBuilder.Length > 0)
                    {
                        typeBuilder.Append("/");
                    }

                    if (type is not JTokenExpression jTokenType)
                    {
                        return(null);
                    }

                    typeBuilder.Append(jTokenType.Value.Value <string>());
                }

                var typeParam = new JTokenExpression(typeBuilder.ToString());

                functionExpression = new FunctionExpression(
                    "resourceId",
                    typeParam.AsEnumerable().Concat(names).ToArray(),
                    new LanguageExpression[] { });
            }

            if (!functionExpression.NameEquals("resourceId") ||
                functionExpression.Parameters.Length < 2)
            {
                return(null);
            }

            var typeString = (functionExpression.Parameters[0] as JTokenExpression)?.Value.Value <string>();

            if (typeString == null)
            {
                return(null);
            }

            var nameExpression = FormatNameExpression(functionExpression.Parameters.Skip(1));

            return(typeString, nameExpression);
        }
        private LanguageExpression ConvertFunction(FunctionCallSyntaxBase functionCall)
        {
            var symbol = context.SemanticModel.GetSymbolInfo(functionCall);

            if (symbol is FunctionSymbol functionSymbol &&
                context.SemanticModel.TypeManager.GetMatchedFunctionOverload(functionCall) is FunctionOverload functionOverload &&
                functionOverload.Evaluator is not null)
            {
                return(ConvertExpression(functionOverload.Evaluator(functionCall, symbol, context.SemanticModel.GetTypeInfo(functionCall))));
            }

            switch (functionCall)
            {
            case FunctionCallSyntax function:
                return(CreateFunction(
                           function.Name.IdentifierName,
                           function.Arguments.Select(a => ConvertExpression(a.Expression))));

            case InstanceFunctionCallSyntax instanceFunctionCall:
                var(baseSymbol, indexExpression) = instanceFunctionCall.BaseExpression switch
                {
                    ArrayAccessSyntax arrayAccessSyntax => (context.SemanticModel.GetSymbolInfo(arrayAccessSyntax.BaseExpression), arrayAccessSyntax.IndexExpression),
                    _ => (context.SemanticModel.GetSymbolInfo(instanceFunctionCall.BaseExpression), null),
                };

                switch (baseSymbol)
                {
                case INamespaceSymbol namespaceSymbol:
                    Debug.Assert(indexExpression is null, "Indexing into a namespace should have been blocked by type analysis");
                    return(CreateFunction(
                               instanceFunctionCall.Name.IdentifierName,
                               instanceFunctionCall.Arguments.Select(a => ConvertExpression(a.Expression))));

                case ResourceSymbol resourceSymbol when context.SemanticModel.ResourceMetadata.TryLookup(resourceSymbol.DeclaringSyntax) is
                    {
                    }

                    resource:
                    if (instanceFunctionCall.Name.IdentifierName.StartsWithOrdinalInsensitively("list"))
                    {
                        var converter = indexExpression is not null?
                                        CreateConverterForIndexReplacement(resource.NameSyntax, indexExpression, instanceFunctionCall) :
                                            this;

                        // Handle list<method_name>(...) method on resource symbol - e.g. stgAcc.listKeys()
                        var convertedArgs        = instanceFunctionCall.Arguments.SelectArray(a => ConvertExpression(a.Expression));
                        var resourceIdExpression = converter.GetFullyQualifiedResourceId(resource);

                        var apiVersion           = resource.TypeReference.ApiVersion ?? throw new InvalidOperationException($"Expected resource type {resource.TypeReference.FormatName()} to contain version");
                        var apiVersionExpression = new JTokenExpression(apiVersion);

                        var listArgs = convertedArgs.Length switch
                        {
                            0 => new LanguageExpression[] { resourceIdExpression, apiVersionExpression, },
                            _ => new LanguageExpression[] { resourceIdExpression, }.Concat(convertedArgs),
                        };

                        return(CreateFunction(instanceFunctionCall.Name.IdentifierName, listArgs));
                    }

                    break;
                }
                throw new InvalidOperationException($"Unrecognized base expression {baseSymbol?.Kind}");

            default:
                throw new NotImplementedException($"Cannot emit unexpected expression of type {functionCall.GetType().Name}");
            }
        }