public override void VisitObjectSyntax(ObjectSyntax syntax) { if (this.bodyType == null) { return; } if (syntax.HasParseErrors()) { return; } // Only visit the object properties if they are required to be deploy time constant. foreach (var(propertyName, propertySyntax) in syntax.ToNamedPropertyDictionary()) { if (this.bodyType.Properties.TryGetValue(propertyName, out var propertyType) && propertyType.Flags.HasFlag(TypePropertyFlags.DeployTimeConstant)) { this.deployTimeConstantScopeSyntax = propertySyntax; } if (this.deployTimeConstantScopeSyntax is not null) { // Reset deployTimeConstantScopeSyntax for nested deploy-time constant properties such as "tags.*". this.deployTimeConstantScopeSyntax = propertySyntax; this.VisitObjectPropertySyntax(propertySyntax); } if (propertyType is not null && propertyType.Flags.HasFlag(TypePropertyFlags.DeployTimeConstant)) { this.deployTimeConstantScopeSyntax = null; } } }
public override void VisitObjectSyntax(ObjectSyntax syntax) { if (syntax.HasParseErrors()) { return; } // Only visit the object properties if they are required to be deploy time constant. foreach (var(propertyName, propertySyntax) in syntax.ToNamedPropertyDictionary()) { var isTopLevelDeployTimeConstantProperty = this.bodyType is not null && this.bodyType.Properties.TryGetValue(propertyName, out var propertyType) && propertyType.Flags.HasFlag(TypePropertyFlags.DeployTimeConstant); if (isTopLevelDeployTimeConstantProperty || this.deployTimeConstantScopeSyntax is not null) { // Also need to reset deployTimeConstantScopeSyntax for nested deploy-time constant properties such as "tags.*". this.deployTimeConstantScopeSyntax = propertySyntax; } // In case there's a nested property whose name matches a deployment constant property name, // for example, "properties.replicaSets[0].location", set bodyType to null to avoid flagging that property. var currentBodyType = this.bodyType; this.bodyType = null; this.VisitObjectPropertySyntax(propertySyntax); // Restore bodyType for the current level. this.bodyType = currentBodyType; if (isTopLevelDeployTimeConstantProperty) { this.deployTimeConstantScopeSyntax = null; } } }
private static TypeSymbol NarrowObjectType(ITypeManager typeManager, ObjectSyntax expression, ObjectType targetType, IDiagnosticWriter diagnosticWriter, bool skipConstantCheck) { // TODO: Short-circuit on any object to avoid unnecessary processing? // TODO: Consider doing the schema check even if there are parse errors // if we have parse errors, there's no point to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return(targetType); } var namedPropertyMap = expression.ToNamedPropertyDictionary(); var missingRequiredProperties = targetType.Properties.Values .Where(p => p.Flags.HasFlag(TypePropertyFlags.Required) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(p => p); if (missingRequiredProperties.Any()) { IPositionable positionable = expression; string blockName = "object"; var parent = typeManager.GetParent(expression); if (parent is ObjectPropertySyntax objectPropertyParent) { positionable = objectPropertyParent.Key; blockName = "object"; } else if (parent is INamedDeclarationSyntax declarationParent) { positionable = declarationParent.Name; blockName = declarationParent.Keyword.Text; } diagnosticWriter.Write(DiagnosticBuilder.ForPosition(positionable).MissingRequiredProperties(ShouldWarn(targetType), missingRequiredProperties, blockName)); } var narrowedProperties = new List <TypeProperty>(); foreach (var declaredProperty in targetType.Properties.Values) { if (namedPropertyMap.TryGetValue(declaredProperty.Name, out var declaredPropertySyntax)) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheck == false && declaredProperty.Flags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants GetCompileTimeConstantViolation(declaredPropertySyntax.Value, diagnosticWriter); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } if (declaredProperty.Flags.HasFlag(TypePropertyFlags.ReadOnly)) { // the declared property is read-only // value cannot be assigned to a read-only property diagnosticWriter.Write(DiagnosticBuilder.ForPosition(declaredPropertySyntax.Key).CannotAssignToReadOnlyProperty(ShouldWarn(targetType), declaredProperty.Name)); narrowedProperties.Add(new TypeProperty(declaredProperty.Name, declaredProperty.TypeReference.Type, declaredProperty.Flags)); continue; } // declared property is specified in the value object // validate type var narrowedType = NarrowTypeInternal( typeManager, declaredPropertySyntax.Value, declaredProperty.TypeReference.Type, diagnosticWriter, GetPropertyMismatchErrorFactory(ShouldWarn(targetType), declaredProperty.Name), skipConstantCheckForProperty, skipTypeErrors: true); narrowedProperties.Add(new TypeProperty(declaredProperty.Name, narrowedType, declaredProperty.Flags)); } else { narrowedProperties.Add(declaredProperty); } } // find properties that are specified on in the expression object but not declared in the schema var extraProperties = expression.Properties .Where(p => !(p.TryGetKeyText() is string keyName) || !targetType.Properties.ContainsKey(keyName)); if (targetType.AdditionalPropertiesType == null) { bool shouldWarn = ShouldWarn(targetType); var validUnspecifiedProperties = targetType.Properties.Values .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(x => x); // extra properties are not allowed by the type foreach (var extraProperty in extraProperties) { Diagnostic error; var builder = DiagnosticBuilder.ForPosition(extraProperty.Key); if (extraProperty.TryGetKeyText() is string keyName) { error = validUnspecifiedProperties.Any() switch { true => SpellChecker.GetSpellingSuggestion(keyName, validUnspecifiedProperties) switch { string suggestedKeyName when suggestedKeyName != null => builder.DisallowedPropertyWithSuggestion(shouldWarn, keyName, targetType, suggestedKeyName), _ => builder.DisallowedPropertyWithPermissibleProperties(shouldWarn, keyName, targetType, validUnspecifiedProperties) },
private static TypeSymbol NarrowObjectType(ITypeManager typeManager, ObjectSyntax expression, ObjectType targetType, IList <Diagnostic> diagnostics, bool skipConstantCheck) { // TODO: Short-circuit on any object to avoid unnecessary processing? // TODO: Consider doing the schema check even if there are parse errors // if we have parse errors, there's no point to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return(targetType); } var namedPropertyMap = expression.ToNamedPropertyDictionary(); var missingRequiredProperties = targetType.Properties.Values .Where(p => p.Flags.HasFlag(TypePropertyFlags.Required) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(p => p) .ConcatString(LanguageConstants.ListSeparator); if (string.IsNullOrEmpty(missingRequiredProperties) == false) { diagnostics.Add(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperties(ShouldWarn(targetType), missingRequiredProperties)); } var narrowedProperties = new List <TypeProperty>(); foreach (var declaredProperty in targetType.Properties.Values) { if (namedPropertyMap.TryGetValue(declaredProperty.Name, out var declaredPropertySyntax)) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheck == false && declaredProperty.Flags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants diagnostics.AddRange(GetCompileTimeConstantViolation(declaredPropertySyntax.Value)); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } if (declaredProperty.Flags.HasFlag(TypePropertyFlags.ReadOnly)) { // the declared property is read-only // value cannot be assigned to a read-only property diagnostics.Add(DiagnosticBuilder.ForPosition(declaredPropertySyntax.Key).CannotAssignToReadOnlyProperty(ShouldWarn(targetType), declaredProperty.Name)); } // declared property is specified in the value object // validate type var narrowedType = NarrowTypeInternal( typeManager, declaredPropertySyntax.Value, declaredProperty.TypeReference.Type, diagnostics, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).PropertyTypeMismatch(ShouldWarn(targetType), declaredProperty.Name, expectedType, actualType), skipConstantCheckForProperty, skipTypeErrors: true); narrowedProperties.Add(new TypeProperty(declaredProperty.Name, narrowedType, declaredProperty.Flags)); } else { narrowedProperties.Add(declaredProperty); } } // find properties that are specified on in the expression object but not declared in the schema var extraProperties = expression.Properties .Where(p => !(p.TryGetKeyText() is string keyName) || !targetType.Properties.ContainsKey(keyName)); if (targetType.AdditionalPropertiesType == null) { var validUnspecifiedProperties = targetType.Properties.Values .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly)) .Where(p => !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(x => x); // extra properties are not allowed by the type foreach (var extraProperty in extraProperties) { Diagnostic error; if (extraProperty.TryGetKeyText() is string keyName) { error = validUnspecifiedProperties.Any() ? DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedPropertyWithPermissibleProperties(ShouldWarn(targetType), keyName, targetType.Name, validUnspecifiedProperties) : DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedProperty(ShouldWarn(targetType), keyName, targetType.Name); } else { error = validUnspecifiedProperties.Any() ? DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedInterpolatedKeyPropertyWithPermissibleProperties(ShouldWarn(targetType), targetType.Name, validUnspecifiedProperties) : DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedInterpolatedKeyProperty(ShouldWarn(targetType), targetType.Name); } diagnostics.AddRange(error.AsEnumerable()); } } else { // extra properties must be assignable to the right type foreach (ObjectPropertySyntax extraProperty in extraProperties) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheckForProperty == false && targetType.AdditionalPropertiesFlags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants diagnostics.AddRange(GetCompileTimeConstantViolation(extraProperty.Value)); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } TypeMismatchErrorFactory typeMismatchErrorFactory; if (extraProperty.TryGetKeyText() is string keyName) { typeMismatchErrorFactory = (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).PropertyTypeMismatch(ShouldWarn(targetType), keyName, expectedType, actualType); } else { typeMismatchErrorFactory = (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).ExpectedValueTypeMismatch(ShouldWarn(targetType), expectedType, actualType); } var narrowedProperty = NarrowTypeInternal( typeManager, extraProperty.Value, targetType.AdditionalPropertiesType.Type, diagnostics, typeMismatchErrorFactory, skipConstantCheckForProperty, skipTypeErrors: true); // TODO should we try and narrow the additional properties type? May be difficult } } return(new NamedObjectType(targetType.Name, targetType.ValidationFlags, narrowedProperties, targetType.AdditionalPropertiesType, targetType.AdditionalPropertiesFlags)); }
private static TypeSymbol NarrowObjectType(ITypeManager typeManager, ObjectSyntax expression, ObjectType targetType, IList <Diagnostic> diagnostics, bool skipConstantCheck) { // TODO: Short-circuit on any object to avoid unnecessary processing? // TODO: Consider doing the schema check even if there are parse errors // if we have parse errors, there's no point to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return(targetType); } var namedPropertyMap = expression.ToNamedPropertyDictionary(); var missingRequiredProperties = targetType.Properties.Values .Where(p => p.Flags.HasFlag(TypePropertyFlags.Required) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(p => p); if (missingRequiredProperties.Any()) { diagnostics.Add(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperties(ShouldWarn(targetType), missingRequiredProperties)); } var narrowedProperties = new List <TypeProperty>(); foreach (var declaredProperty in targetType.Properties.Values) { if (namedPropertyMap.TryGetValue(declaredProperty.Name, out var declaredPropertySyntax)) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheck == false && declaredProperty.Flags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants diagnostics.AddRange(GetCompileTimeConstantViolation(declaredPropertySyntax.Value)); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } if (declaredProperty.Flags.HasFlag(TypePropertyFlags.ReadOnly)) { // the declared property is read-only // value cannot be assigned to a read-only property diagnostics.Add(DiagnosticBuilder.ForPosition(declaredPropertySyntax.Key).CannotAssignToReadOnlyProperty(ShouldWarn(targetType), declaredProperty.Name)); } TypeMismatchErrorFactory typeMismatchErrorFactory = (expectedType, actualType, errorExpression) => { var builder = DiagnosticBuilder.ForPosition(errorExpression); var shouldWarn = ShouldWarn(targetType); if (actualType is StringLiteralType) { string?suggestedStringLiteral = null; if (expectedType is StringLiteralType) { suggestedStringLiteral = SpellChecker.GetSpellingSuggestion(actualType.Name, expectedType.Name.AsEnumerable()); } if (expectedType is UnionType unionType && unionType.Members.All(typeReference => typeReference.Type is StringLiteralType)) { var stringLiteralCandidates = unionType.Members.Select(typeReference => typeReference.Type.Name).OrderBy(x => x); suggestedStringLiteral = SpellChecker.GetSpellingSuggestion(actualType.Name, stringLiteralCandidates); } if (suggestedStringLiteral != null) { return(builder.PropertyStringLiteralMismatchWithSuggestion(shouldWarn, declaredProperty.Name, expectedType, actualType.Name, suggestedStringLiteral)); } } return(builder.PropertyTypeMismatch(shouldWarn, declaredProperty.Name, expectedType, actualType)); }; // declared property is specified in the value object // validate type var narrowedType = NarrowTypeInternal( typeManager, declaredPropertySyntax.Value, declaredProperty.TypeReference.Type, diagnostics, typeMismatchErrorFactory, skipConstantCheckForProperty, skipTypeErrors: true); narrowedProperties.Add(new TypeProperty(declaredProperty.Name, narrowedType, declaredProperty.Flags)); } else { narrowedProperties.Add(declaredProperty); } } // find properties that are specified on in the expression object but not declared in the schema var extraProperties = expression.Properties .Where(p => !(p.TryGetKeyText() is string keyName) || !targetType.Properties.ContainsKey(keyName)); if (targetType.AdditionalPropertiesType == null) { bool shouldWarn = ShouldWarn(targetType); var validUnspecifiedProperties = targetType.Properties.Values .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(x => x); // extra properties are not allowed by the type foreach (var extraProperty in extraProperties) { Diagnostic error; var builder = DiagnosticBuilder.ForPosition(extraProperty.Key); if (extraProperty.TryGetKeyText() is string keyName) { error = validUnspecifiedProperties.Any() switch { true => SpellChecker.GetSpellingSuggestion(keyName, validUnspecifiedProperties) switch { string suggestedKeyName when suggestedKeyName != null => builder.DisallowedPropertyWithSuggestion(shouldWarn, keyName, targetType, suggestedKeyName), _ => builder.DisallowedPropertyWithPermissibleProperties(shouldWarn, keyName, targetType, validUnspecifiedProperties) },