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)); }
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"); } }
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>())); }
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}"); } }