Esempio n. 1
0
        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;
        }
Esempio n. 2
0
        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);
                }
            }
        }