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;
                }
            }
        }
Esempio n. 3
0
        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)
                            },
Esempio n. 4
0
        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));
        }
Esempio n. 5
0
        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)
                            },