chromium/third_party/blink/renderer/bindings/scripts/validator/rules/extended_attribute_descriptor.py

# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import enum

import web_idl


class ExtendedAttributeDescriptor(object):
    class Target(enum.Enum):
        ASYNC_ITERABLE = web_idl.AsyncIterable
        ATTRIBUTE = web_idl.Attribute
        CALLBACK_FUNCTION = web_idl.CallbackFunction
        CALLBACK_INTERFACE = web_idl.CallbackInterface
        CONSTANT = web_idl.Constant
        CONSTRUCTOR = web_idl.Constructor
        DICTIONARY = web_idl.Dictionary
        DICTIONARY_MEMBER = web_idl.DictionaryMember
        INTERFACE = web_idl.Interface
        ITERABLE = web_idl.Iterable
        LEGACY_WINDOW_ALIAS = web_idl.LegacyWindowAlias
        NAMESPACE = web_idl.Namespace
        OPERATION = web_idl.Operation
        TYPE = web_idl.IdlType

    class Form(enum.Enum):
        # https://webidl.spec.whatwg.org/#idl-extended-attributes
        NO_ARGS = enum.auto()  # [ExtAttr]
        IDENT = enum.auto()  # [ExtAttr=Value]
        IDENT_LIST = enum.auto()  # [ExtAttr=(Value1, ...)]
        ARG_LIST = enum.auto()  # [ExtAttr(V1L V1R, ...)]
        NAMED_ARG_LIST = enum.auto()  # [ExtAttr=Name(V1L V1R, ...)]

    def __init__(self,
                 name,
                 applicable_to=None,
                 forms=None,
                 values=None,
                 post_validate=None):
        assert isinstance(name, str)
        assert isinstance(applicable_to, list) and all(
            isinstance(target, ExtendedAttributeDescriptor.Target)
            for target in applicable_to)
        assert forms is None or isinstance(
            forms, ExtendedAttributeDescriptor.Form) or (isinstance(
                forms, list) and all(
                    isinstance(form, ExtendedAttributeDescriptor.Form)
                    for form in forms))
        assert values is None or (isinstance(values, list) and all(
            isinstance(value, str) for value in values))
        assert post_validate is None or callable(post_validate)

        self._name = name
        # self._applicable_to is a list of valid target object's types, e.g.
        # web_idl.Attribute, web_idl.Constant, etc.
        self._applicable_to = tuple(map(lambda e: e.value, applicable_to))
        # self._forms is a list of valid forms.
        if forms is None:
            self._forms = [ExtendedAttributeDescriptor.Form.NO_ARGS]
        elif not isinstance(forms, list):
            self._forms = [forms]
        else:
            self._forms = forms
        # self._values is a list of valid "ident" values
        if values is None:
            self._values = None
        else:
            assert (ExtendedAttributeDescriptor.Form.IDENT in self._forms or
                    ExtendedAttributeDescriptor.Form.IDENT_LIST in self._forms)
            self._values = values
        # self._post_validate is a callable or None.
        self._post_validate = post_validate

    @property
    def name(self):
        return self._name

    def validate(self, assert_, target_object, ext_attr):
        T = ExtendedAttributeDescriptor.Target
        F = ExtendedAttributeDescriptor.Form

        failure_count = [0]

        def _assert(condition, text, *args, **kwargs):
            if not condition:
                failure_count[0] = failure_count[0] + 1
                assert_(condition, text, *args, **kwargs)

        # applicable_to
        _assert(isinstance(target_object, self._applicable_to),
                "[{}] is not applicable to {}.", self._name,
                target_object.__class__.__name__)

        # forms
        if ext_attr.has_values:
            if not ext_attr.values:
                _assert(F.NO_ARGS in self._forms,
                        "[{}] needs an identifier or an argument list.",
                        self._name)
            elif F.IDENT_LIST in self._forms:
                pass
            elif F.IDENT in self._forms:
                _assert(
                    len(ext_attr.values) == 1,
                    "[{}] doesn't take an identifier list.", self._name)
            elif F.ARG_LIST in self._forms or F.NAMED_ARG_LIST in self._forms:
                _assert(False, "[{}] needs an argument list.", self._name)
            else:  # F.NO_ARGS only
                _assert(False, "[{}] doesn't take an identifier.", self._name)
        if ext_attr.has_arguments:
            _assert(
                F.ARG_LIST in self._forms or F.NAMED_ARG_LIST in self._forms,
                "[{}] doesn't take an argument list.", self._name)
        if ext_attr.has_name:
            _assert(F.NAMED_ARG_LIST in self._forms,
                    "[{}] doesn't take an named argument list.", self._name)

        # values
        if self._values:
            for value in ext_attr.values:
                _assert(value in self._values, "[{}={}] is not supported.",
                        self._name, value)

        # post_validate
        if self._post_validate:
            if failure_count[0] == 0:
                self._post_validate(assert_, target_object, ext_attr)