protected override SyntaxBase ReplaceStringSyntax(StringSyntax syntax) { var declaredType = semanticModel.GetDeclaredType(syntax); if (semanticModel.GetTypeInfo(syntax) is not StringLiteralType actualType) { return(base.ReplaceStringSyntax(syntax)); } if (declaredType is null || TypeValidator.AreTypesAssignable(actualType, declaredType)) { return(base.ReplaceStringSyntax(syntax)); } var stringLiteralCandidates = Enumerable.Empty <StringLiteralType>(); if (declaredType is StringLiteralType stringLiteralType) { stringLiteralCandidates = stringLiteralType.AsEnumerable(); } else if (declaredType is UnionType unionType && unionType.Members.All(x => x.Type is StringLiteralType)) { stringLiteralCandidates = unionType.Members.Select(x => (StringLiteralType)x.Type); } var insensitiveMatch = stringLiteralCandidates.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.Name, actualType.Name)); if (insensitiveMatch == null) { return(base.ReplaceStringSyntax(syntax)); } return(SyntaxFactory.CreateStringLiteral(insensitiveMatch.RawStringValue)); }
public override void VisitStringSyntax(StringSyntax syntax) => AssignType(syntax, () => { if (syntax.TryGetLiteralValue() is string literalValue) { // uninterpolated strings have a known type return(new StringLiteralType(literalValue)); } var errors = new List <ErrorDiagnostic>(); foreach (var interpolatedExpression in syntax.Expressions) { var expressionType = typeManager.GetTypeInfo(interpolatedExpression); CollectErrors(errors, expressionType); } if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } // normally we would also do an assignability check, but we allow "any" type in string interpolation expressions // so the assignability check cannot possibly fail (we already collected type errors from the inner expressions at this point) return(LanguageConstants.String); });
private TypeSymbol GetStringType(TypeManagerContext context, StringSyntax @string) { if (@string.IsInterpolated() == false) { // uninterpolated strings have a known type return(LanguageConstants.String); } var errors = new List <ErrorDiagnostic>(); foreach (var interpolatedExpression in @string.Expressions) { var expressionType = this.GetTypeInfoInternal(context, interpolatedExpression); CollectErrors(errors, expressionType); } if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } // normally we would also do an assignability check, but we allow "any" type in string interpolation expressions // so the assignability check cannot possibly fail (we already collected type errors from the inner expressions at this point) return(LanguageConstants.String); }
public ResourceDefinition(string resourceName, ResourceSymbol?resourceScope, string resourceTypeFQDN, StringSyntax resourceNamePropertyValue) { ResourceName = resourceName; ResourceScope = resourceScope; ResourceTypeFQDN = resourceTypeFQDN; ResourceNamePropertyValue = resourceNamePropertyValue; }
public ResourceDefinition(string resourceName, ResourceMetadata?resourceScope, string fullyQualifiedResourceType, StringSyntax resourceNamePropertyValue) { ResourceName = resourceName; ResourceScope = resourceScope; FullyQualifiedResourceType = fullyQualifiedResourceType; ResourceNamePropertyValue = resourceNamePropertyValue; }
public ModuleDefinition(string moduleName, ResourceScope modulePropertyScopeType, ImmutableArray <StringSyntax?>?modulePropertyScopeValue, StringSyntax modulePropertyNameValue) { ModuleName = moduleName; ModulePropertyScopeType = modulePropertyScopeType; ModulePropertyScopeValue = modulePropertyScopeValue; ModulePropertyNameValue = modulePropertyNameValue; }
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 void Render(StringSyntax @string) { Render(@string.StartStringSymbol); if (@string.StringNode != null) { Render(@string.StringNode); } Render(@string.EndStringSymbol); }
public SyntaxBase?TryGetReplacementStringSyntax(StringSyntax parent, StringSyntax child, ResourceSymbol parentResourceSymbol) { if (parent.SegmentValues.Length > child.SegmentValues.Length || parent.Expressions.Length > child.Expressions.Length) { return(null); } for (var i = 0; i < parent.Expressions.Length; i++) { var childSymbol = semanticModel.GetSymbolInfo(child.Expressions[i]); var parentSymbol = semanticModel.GetSymbolInfo(parent.Expressions[i]); if (childSymbol == null || childSymbol != parentSymbol) { return(null); } } for (var i = 0; i < parent.SegmentValues.Length - 1; i++) { if (child.SegmentValues[i] != parent.SegmentValues[i]) { return(null); } } var finalIndex = parent.SegmentValues.Length - 1; if (!child.SegmentValues[finalIndex].StartsWith(parent.SegmentValues[finalIndex], StringComparison.Ordinal)) { return(null); } var finalSegmentSuffix = child.SegmentValues[finalIndex].Substring(parent.SegmentValues[finalIndex].Length); if (finalSegmentSuffix.Length == 0 || finalSegmentSuffix[0] != '/') { return(null); } var newNameValues = new [] { finalSegmentSuffix.Substring(1) }.Concat(child.SegmentValues.Skip(finalIndex + 1)).ToArray(); var newExpressions = child.Expressions.Skip(finalIndex).ToArray(); if (newNameValues.Length == 2 && newNameValues[0] == "" && newNameValues[1] == "") { // return "expr" rather than "'${expr}'" return(newExpressions[0]); } return(SyntaxFactory.CreateString(newNameValues, newExpressions)); }
protected override SyntaxBase ReplaceObjectSyntax(ObjectSyntax syntax) { var declaredType = semanticModel.GetDeclaredType(syntax); if (declaredType is not ObjectType objectType) { return(base.ReplaceObjectSyntax(syntax)); } var newChildren = new List <SyntaxBase>(); foreach (var child in syntax.Children) { if (child is ObjectPropertySyntax objectProperty && objectProperty.TryGetKeyText() is string propertyKey && !objectType.Properties.ContainsKey(propertyKey)) { var insensitivePropertyKey = objectType.Properties.Keys.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x, propertyKey)); if (insensitivePropertyKey != null) { SyntaxBase newKeySyntax; if (Regex.IsMatch(insensitivePropertyKey, "^[a-zA-Z][a-zA-Z0-9_]*$")) { newKeySyntax = new IdentifierSyntax(new Token(TokenType.Identifier, new TextSpan(0, 0), insensitivePropertyKey, Enumerable.Empty <SyntaxTrivia>(), Enumerable.Empty <SyntaxTrivia>())); } else { var stringToken = new Token(TokenType.StringComplete, new TextSpan(0, 0), StringUtils.EscapeBicepString(insensitivePropertyKey), Enumerable.Empty <SyntaxTrivia>(), Enumerable.Empty <SyntaxTrivia>()); newKeySyntax = new StringSyntax(stringToken.AsEnumerable(), Enumerable.Empty <SyntaxBase>(), insensitivePropertyKey.AsEnumerable()); } newChildren.Add(new ObjectPropertySyntax( newKeySyntax, objectProperty.Colon, Rewrite(objectProperty.Value))); continue; } } newChildren.Add(Rewrite(child)); } if (Enumerable.SequenceEqual(newChildren, syntax.Children)) { return(base.ReplaceObjectSyntax(syntax)); } return(new ObjectSyntax( syntax.OpenBrace, newChildren, syntax.CloseBrace)); }
public override void VisitStringSyntax(StringSyntax syntax) { var disallowedHost = syntax.SegmentValues.Select(s => FindEnvironmentUrlInString(s)) .Where(span => span != null) .FirstOrDefault(); if (disallowedHost != null) { this.DisallowedHostSpans.Add((disallowedHost, syntax.Span)); } base.VisitStringSyntax(syntax); }
private DeclaredTypeAssignment?GetStringType(StringSyntax syntax) { var parent = this.binder.GetParent(syntax); // we are only handling paths in the AST that are going to produce a declared type // strings can exist under a variable declaration, but variables don't have declared types, // so we don't need to check that case if (parent is ObjectPropertySyntax || parent is ArrayItemSyntax) { // this string is a value of the property // the declared type should be the same as the string and we should propagate the flags return(GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax)); } return(null); }
public static string BuildFormatString(StringSyntax syntax) { var stringBuilder = new StringBuilder(); var values = syntax.SegmentValues; for (var i = 0; i < values.Length - 1; i++) { AppendAndEscapeCurlies(stringBuilder, values[i]); stringBuilder.Append('{'); stringBuilder.Append(i); stringBuilder.Append('}'); } AppendAndEscapeCurlies(stringBuilder, values[values.Length - 1]); return(stringBuilder.ToString()); }
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 SyntaxBase?TryGetReplacementChildName(StringSyntax childName, SyntaxBase parentName, ResourceSymbol parentResourceSymbol) { switch (parentName) { case VariableAccessSyntax parentVarAccess: { if (childName.Expressions.FirstOrDefault() is not VariableAccessSyntax childVarAccess || semanticModel.GetSymbolInfo(parentVarAccess) != semanticModel.GetSymbolInfo(childVarAccess)) { return(null); } if (!childName.SegmentValues[1].StartsWith("/")) { return(null); } var newName = SyntaxFactory.CreateString( new [] { childName.SegmentValues[1].Substring(1) }.Concat(childName.SegmentValues.Skip(2)), childName.Expressions.Skip(1)); return(newName); } case StringSyntax parentString: { if (TryGetReplacementStringSyntax(parentString, childName, parentResourceSymbol) is not { } newName) { return(null); } return(newName); } } return(null); }
public override void VisitStringSyntax(StringSyntax syntax) { foreach (var segment in syntax.SegmentValues) { var exclusionMatches = exclusionRegex.Matches(segment); // does this segment have a host match foreach (Match match in this.hostRegex.Matches(segment)) { // exclusion is found containing the host match var isExcluded = exclusionMatches.Any(exclusionMatch => match.Index > exclusionMatch.Index && match.Index + match.Length <= exclusionMatch.Index + exclusionMatch.Length); if (!isExcluded) { // create a span for the specific identified instance // to allow for multiple instances in a single syntax this.DisallowedHostSpans[new TextSpan(syntax.Span.Position + match.Index, match.Length)] = match.Value; } } base.VisitStringSyntax(syntax); } }
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)); }
protected override SyntaxBase ReplaceResourceDeclarationSyntax(ResourceDeclarationSyntax syntax) { if (syntax.TryGetBody() is not ObjectSyntax resourceBody || resourceBody.SafeGetPropertyByName("name") is not ObjectPropertySyntax resourceNameProp || resourceNameProp.Value is not StringSyntax resourceName) { return(syntax); } if (semanticModel.GetSymbolInfo(syntax) is not ResourceSymbol resourceSymbol || resourceSymbol.Type is not ResourceType resourceType) { return(syntax); } if (resourceType.TypeReference.Types.Length < 2) { // we're only looking for child resources here return(syntax); } foreach (var otherResourceSymbol in semanticModel.Root.GetAllResourceDeclarations()) { if (otherResourceSymbol.Type is not ResourceType otherResourceType || otherResourceType.TypeReference.Types.Length != resourceType.TypeReference.Types.Length - 1 || !resourceType.TypeReference.TypesString.StartsWith(otherResourceType.TypeReference.TypesString, StringComparison.OrdinalIgnoreCase)) { continue; } // The other resource is a parent type to this one. check if we can refactor the name. if (otherResourceSymbol.DeclaringResource.TryGetBody() is not ObjectSyntax otherResourceBody || otherResourceBody.SafeGetPropertyByName("name") is not ObjectPropertySyntax otherResourceNameProp) { continue; } StringSyntax replacementStringSyntax; if (otherResourceNameProp.Value is StringSyntax otherResourceName) { var newStringSyntax = TryGetReplacementStringSyntax(otherResourceName, resourceName, otherResourceSymbol); if (newStringSyntax == null) { continue; } replacementStringSyntax = newStringSyntax; } else if (otherResourceNameProp.Value is VariableAccessSyntax parentVarAccess && resourceName.Expressions.FirstOrDefault() is VariableAccessSyntax childVarAccess) { if (semanticModel.GetSymbolInfo(parentVarAccess) != semanticModel.GetSymbolInfo(childVarAccess)) { continue; } var otherResourceIdentifier = new Token(TokenType.Identifier, new TextSpan(0, 0), otherResourceSymbol.Name, Enumerable.Empty <SyntaxTrivia>(), Enumerable.Empty <SyntaxTrivia>()); var nameProperty = new Token(TokenType.Identifier, new TextSpan(0, 0), "name", Enumerable.Empty <SyntaxTrivia>(), Enumerable.Empty <SyntaxTrivia>()); var replacementExpression = new PropertyAccessSyntax( new VariableAccessSyntax(new IdentifierSyntax(otherResourceIdentifier)), new Token(TokenType.Dot, new TextSpan(0, 0), ".", Enumerable.Empty <SyntaxTrivia>(), Enumerable.Empty <SyntaxTrivia>()), new IdentifierSyntax(nameProperty)); replacementStringSyntax = new StringSyntax( resourceName.StringTokens, replacementExpression.AsEnumerable().Concat(resourceName.Expressions.Skip(1)), resourceName.SegmentValues); }
/// <summary> /// Checks if the syntax node contains an interpolated string or a literal string. /// </summary> /// <param name="syntax">The string syntax node</param> public static bool IsInterpolated(this StringSyntax syntax) => syntax.SegmentValues.Length > 1;
/// <summary> /// Try to get the string literal value for a syntax node. Returns null if the string is interpolated. /// </summary> /// <param name="syntax">The string syntax node</param> public static string?TryGetLiteralValue(this StringSyntax syntax) => syntax.IsInterpolated() ? null : syntax.SegmentValues[0];