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; } } }
private static IEnumerable <ErrorDiagnostic> GetDiscriminatedObjectAssignmentDiagnostics(ITypeManager typeManager, ObjectSyntax expression, DiscriminatedObjectType targetType, bool skipConstantCheck) { // 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()) { yield break; } var propertyMap = expression.ToPropertyDictionary(); if (!propertyMap.TryGetValue(targetType.DiscriminatorKey, out var discriminatorProperty)) { // object doesn't contain the discriminator field yield return(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperty(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType)); yield break; } // At some point in the future we may want to relax the expectation of a string literal key, and allow a generic string. // In this case, the best we can do is validate against the union of all the settable properties. // Let's not do this just yet, and see if a use-case arises. var discriminatorType = typeManager.GetTypeInfo(discriminatorProperty.Value); if (!(discriminatorType is StringLiteralType stringLiteralDiscriminator)) { yield return(DiagnosticBuilder.ForPosition(expression).PropertyTypeMismatch(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); yield break; } if (!targetType.UnionMembersByKey.TryGetValue(stringLiteralDiscriminator.Name, out var selectedObjectReference)) { // no matches yield return(DiagnosticBuilder.ForPosition(discriminatorProperty.Value).PropertyTypeMismatch(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); yield break; } if (!(selectedObjectReference.Type is ObjectType selectedObjectType)) { throw new InvalidOperationException($"Discriminated type {targetType.Name} contains non-object member"); } // we have a match! foreach (var diagnostic in GetObjectAssignmentDiagnostics(typeManager, expression, selectedObjectType, skipConstantCheck)) { yield return(diagnostic); } yield break; }
private static TypeSymbol NarrowDiscriminatedObjectType(ITypeManager typeManager, ObjectSyntax expression, DiscriminatedObjectType targetType, IList <Diagnostic> diagnostics, bool skipConstantCheck) { // 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(LanguageConstants.Any); } var discriminatorProperty = expression.Properties.FirstOrDefault(x => LanguageConstants.IdentifierComparer.Equals(x.TryGetKeyText(), targetType.DiscriminatorKey)); if (discriminatorProperty == null) { // object doesn't contain the discriminator field diagnostics.Add(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperty(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType)); return(LanguageConstants.Any); } // At some point in the future we may want to relax the expectation of a string literal key, and allow a generic string. // In this case, the best we can do is validate against the union of all the settable properties. // Let's not do this just yet, and see if a use-case arises. var discriminatorType = typeManager.GetTypeInfo(discriminatorProperty.Value); if (!(discriminatorType is StringLiteralType stringLiteralDiscriminator)) { diagnostics.Add(DiagnosticBuilder.ForPosition(expression).PropertyTypeMismatch(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return(LanguageConstants.Any); } if (!targetType.UnionMembersByKey.TryGetValue(stringLiteralDiscriminator.Name, out var selectedObjectReference)) { // no matches diagnostics.Add(DiagnosticBuilder.ForPosition(discriminatorProperty.Value).PropertyTypeMismatch(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return(LanguageConstants.Any); } if (!(selectedObjectReference.Type is ObjectType selectedObjectType)) { throw new InvalidOperationException($"Discriminated type {targetType.Name} contains non-object member"); } // we have a match! return(NarrowObjectType(typeManager, expression, selectedObjectType, diagnostics, skipConstantCheck)); }
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 void GetObjectAssignmentDiagnostics(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; } var propertyMap = expression.ToPropertyDictionary(); var missingRequiredProperties = targetType.Properties.Values .Where(p => p.Flags.HasFlag(TypePropertyFlags.Required) && propertyMap.ContainsKey(p.Name) == false) .Select(p => p.Name) .OrderBy(p => p) .ConcatString(LanguageConstants.ListSeparator); if (string.IsNullOrEmpty(missingRequiredProperties) == false) { diagnostics.Add(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperties(missingRequiredProperties)); } foreach (var declaredProperty in targetType.Properties.Values) { if (propertyMap.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(declaredProperty.Name)); } // declared property is specified in the value object // validate type GetExpressionAssignmentDiagnosticsInternal( typeManager, declaredPropertySyntax.Value, declaredProperty.TypeReference.Type, diagnostics, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).PropertyTypeMismatch(declaredProperty.Name, expectedType, actualType), skipConstantCheckForProperty, skipTypeErrors: true); } } // find properties that are specified on in the expression object but not declared in the schema var extraProperties = expression.Properties .Select(p => p.GetKeyText()) .Except(targetType.Properties.Values.Select(p => p.Name), LanguageConstants.IdentifierComparer) .Select(name => propertyMap[name]); if (targetType.AdditionalPropertiesType == null) { var validUnspecifiedProperties = targetType.Properties.Values .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly)) .Select(p => p.Name) .Except(expression.Properties.Select(p => p.GetKeyText()), LanguageConstants.IdentifierComparer) .OrderBy(x => x); // extra properties are not allowed by the type foreach (var extraProperty in extraProperties) { var error = validUnspecifiedProperties.Any() ? DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedPropertyWithPermissibleProperties(extraProperty.GetKeyText(), targetType.Name, validUnspecifiedProperties) : DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedProperty(extraProperty.GetKeyText(), 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; } GetExpressionAssignmentDiagnosticsInternal( typeManager, extraProperty.Value, targetType.AdditionalPropertiesType.Type, diagnostics, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).PropertyTypeMismatch(extraProperty.GetKeyText(), expectedType, actualType), skipConstantCheckForProperty, skipTypeErrors: true); } } }
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 NarrowDiscriminatedObjectType(ITypeManager typeManager, ObjectSyntax expression, DiscriminatedObjectType targetType, IDiagnosticWriter diagnosticWriter, bool skipConstantCheck) { // 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(LanguageConstants.Any); } var discriminatorProperty = expression.Properties.FirstOrDefault(x => LanguageConstants.IdentifierComparer.Equals(x.TryGetKeyText(), targetType.DiscriminatorKey)); if (discriminatorProperty == null) { // object doesn't contain the discriminator field diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperty(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType)); var propertyKeys = expression.Properties .Select(x => x.TryGetKeyText()) .Where(key => !string.IsNullOrEmpty(key)) .Select(key => key !); // do a reverse lookup to check if there's any misspelled discriminator key var misspelledDiscriminatorKey = SpellChecker.GetSpellingSuggestion(targetType.DiscriminatorKey, propertyKeys); if (misspelledDiscriminatorKey != null) { diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).DisallowedPropertyWithSuggestion(ShouldWarn(targetType), misspelledDiscriminatorKey, targetType.DiscriminatorKeysUnionType, targetType.DiscriminatorKey)); } return(LanguageConstants.Any); } // At some point in the future we may want to relax the expectation of a string literal key, and allow a generic string. // In this case, the best we can do is validate against the union of all the settable properties. // Let's not do this just yet, and see if a use-case arises. var discriminatorType = typeManager.GetTypeInfo(discriminatorProperty.Value); if (!(discriminatorType is StringLiteralType stringLiteralDiscriminator)) { diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).PropertyTypeMismatch(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return(LanguageConstants.Any); } if (!targetType.UnionMembersByKey.TryGetValue(stringLiteralDiscriminator.Name, out var selectedObjectReference)) { // no matches var discriminatorCandidates = targetType.UnionMembersByKey.Keys.OrderBy(x => x); string?suggestedDiscriminator = SpellChecker.GetSpellingSuggestion(stringLiteralDiscriminator.Name, discriminatorCandidates); var builder = DiagnosticBuilder.ForPosition(discriminatorProperty.Value); bool shouldWarn = ShouldWarn(targetType); diagnosticWriter.Write(suggestedDiscriminator != null ? builder.PropertyStringLiteralMismatchWithSuggestion(shouldWarn, targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, stringLiteralDiscriminator.Name, suggestedDiscriminator) : builder.PropertyTypeMismatch(shouldWarn, targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return(LanguageConstants.Any); } if (!(selectedObjectReference.Type is ObjectType selectedObjectType)) { throw new InvalidOperationException($"Discriminated type {targetType.Name} contains non-object member"); } // we have a match! return(NarrowObjectType(typeManager, expression, selectedObjectType, diagnosticWriter, skipConstantCheck)); }
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) },