Commit fb396f70 authored by Hitoshi Yoshida's avatar Hitoshi Yoshida Committed by Commit Bot

bindings: Generate a IDL information collection per component

This CL defines a new set of Web IDL based classes under web_idl/.
Collecitor class is the main API, which reads IDL files and
stacks converted information.

This CL also updates some BUILD.gn files to generate an
intermediate dump file per component, which includes all
information in IDL files in the component.


Bug: 650150, 727971, 579896
Change-Id: Idc98be4485855afc7797b7fc3af21fb7ee68f0ed
Reviewed-on: https://chromium-review.googlesource.com/648534
Commit-Queue: Hitoshi Yoshida <peria@chromium.org>
Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarKenichi Ishibashi <bashi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#522378}
parent f97b5870
...@@ -39,3 +39,10 @@ generate_global_constructors("core_global_constructors_idls") { ...@@ -39,3 +39,10 @@ generate_global_constructors("core_global_constructors_idls") {
":core_global_objects", ":core_global_objects",
] ]
} }
generate_web_idl_collection("core_web_idl_collection") {
sources = core_static_interface_idl_files - core_testing_definition_idl_files
output = "WebIdlCollectionForCore.pickle"
component = "core"
output_dir = bindings_core_output_dir
}
...@@ -198,3 +198,10 @@ source_set("generated") { ...@@ -198,3 +198,10 @@ source_set("generated") {
"//v8", "//v8",
] ]
} }
generate_web_idl_collection("modules_web_idl_collection") {
sources = modules_definition_idl_files + modules_dependency_idl_files
output = "WebIdlCollectionForModules.pickle"
component = "modules"
output_dir = bindings_modules_output_dir
}
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates a data collection of IDL information per component.
In this data collection, we use identifier strings to point IDL definitions
(i.e. interface, dictionary, namespace, etc.) instead of references, because
some referred definitions can be in other components.
"""
import blink_idl_parser
import optparse
import utilities
from web_idl.collector import Collector
def parse_options():
parser = optparse.OptionParser()
parser.add_option('--idl-list-file', help='a file path which lists IDL file paths to process')
parser.add_option('--component', help='decide which component to collect IDLs', default=None)
parser.add_option('--output', help='pickle file of IDL definition')
options, args = parser.parse_args()
if options.idl_list_file is None:
parser.error('Must specify a file listing IDL files using --idl-files-list.')
if options.output is None:
parser.error('Must specify a pickle file to output using --output.')
return options, args
def main():
options, _ = parse_options()
idl_file_names = utilities.read_idl_files_list_from_file(options.idl_list_file, False)
parser = blink_idl_parser.BlinkIDLParser()
collector = Collector(component=options.component, parser=parser)
collection = collector.collect_from_idl_files(idl_file_names)
utilities.write_pickle_file(options.output, collection)
if __name__ == '__main__':
main()
...@@ -58,6 +58,29 @@ idl_compiler_files = ...@@ -58,6 +58,29 @@ idl_compiler_files =
], ],
"abspath") "abspath")
web_idl_scripts = get_path_info([
"web_idl/argument.py",
"web_idl/attribute.py",
"web_idl/callback_function.py",
"web_idl/collection.py",
"web_idl/collector.py",
"web_idl/constant.py",
"web_idl/dictionary.py",
"web_idl/ecma_script_types.py",
"web_idl/enumeration.py",
"web_idl/extended_attribute.py",
"web_idl/idl_types.py",
"web_idl/implements.py",
"web_idl/interface.py",
"web_idl/literal_token.py",
"web_idl/namespace.py",
"web_idl/operation.py",
"web_idl/typedef.py",
"web_idl/utilities.py",
"web_idl/idl_definition_builder.py",
],
"abspath")
# Calls the compute_interfaces_info_individual script. # Calls the compute_interfaces_info_individual script.
# #
# Parameters: # Parameters:
...@@ -540,3 +563,27 @@ template("generate_origin_trial_features") { ...@@ -540,3 +563,27 @@ template("generate_origin_trial_features") {
[ "//third_party/WebKit/Source/bindings/modules:interfaces_info" ] [ "//third_party/WebKit/Source/bindings/modules:interfaces_info" ]
} }
} }
template("generate_web_idl_collection") {
action(target_name) {
script = "//third_party/WebKit/Source/bindings/scripts/generate_web_idl_collection.py"
output_dir = invoker.output_dir
output_file_name = invoker.output
output_path = "${output_dir}/${output_file_name}"
inputs = web_idl_scripts + invoker.sources
outputs = [
output_path,
]
# List input file names in a temporary file.
response_file_contents = rebase_path(invoker.sources, root_build_dir)
args = [
"--idl-list-file",
"{{response_file_name}}",
"--component",
invoker.component,
"--output",
rebase_path(output_path, root_build_dir),
]
}
}
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
class Argument(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._type = kwargs.pop('type')
self._is_optional = kwargs.pop('is_optional', False)
self._is_variadic = kwargs.pop('is_variadic', False)
self._default_value = kwargs.pop('default_value', None)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def type(self):
return self._type
@property
def is_optional(self):
return self._is_optional
@property
def is_variadic(self):
return self._is_variadic
@property
def default_value(self):
return self._default_value
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .idl_types import RecordType
from .idl_types import SequenceType
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-attributes
class Attribute(object):
_INVALID_TYPES = frozenset([SequenceType, RecordType])
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._type = kwargs.pop('type')
self._is_static = kwargs.pop('is_static', False)
self._is_readonly = kwargs.pop('is_readonly', False)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
if type(self.type) in Attribute._INVALID_TYPES:
raise ValueError('The type of an attribute must not be either of sequence<T> and record<K,V>.')
@property
def identifier(self):
return self._identifier
@property
def type(self):
return self._type
@property
def is_static(self):
return self._is_static
@property
def is_readonly(self):
return self._is_readonly
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-callback-functions
class CallbackFunction(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._return_type = kwargs.pop('return_type')
self._arguments = tuple(kwargs.pop('arguments', []))
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def return_type(self):
return self._return_type
@property
def arguments(self):
return self._arguments
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-interfaces
class CallbackInterface(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._attributes = tuple(kwargs.pop('attributes', []))
self._operations = tuple(kwargs.pop('operations', []))
self._constants = tuple(kwargs.pop('constants', []))
self._inherited_interface_name = kwargs.pop('inherited_interface_name', None)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
if any(attribute.is_static for attribute in self.attributes):
raise ValueError('Static attributes must not be defined on a callback interface')
if any(operation.is_static for operation in self.operations):
raise ValueError('Static operations must not be defined on a callback interface')
@property
def identifier(self):
return self._identifier
@property
def attributes(self):
return self._attributes
@property
def operations(self):
return self._operations
@property
def constants(self):
return self._constants
@property
def inherited_interface_name(self):
return self._inherited_interface_name
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .callback_function import CallbackFunction
from .callback_interface import CallbackInterface
from .dictionary import Dictionary
from .enumeration import Enumeration
from .implements import Implements
from .interface import Interface
from .namespace import Namespace
from .typedef import Typedef
class Collection(object):
"""
Collection class stores Web IDL definitions and some meta information.
"""
def __init__(self, component=None):
self._interfaces = {}
self._callback_interfaces = {}
self._namespaces = {}
self._dictionaries = {}
self._enumerations = {}
self._callback_functions = {}
self._typedefs = {}
# In spec, different partial definitions can have same identifiers.
# So they are stored in a list, which is indexed by the identifier.
# i.e. {'identifer': [definition, definition, ...]}
self._partial_interfaces = {}
self._partial_namespaces = {}
self._partial_dictionaries = {}
# Implements statements are not named definitions.
self._implements = []
# These members are not in spec., but they are necessary for code generators.
self._metadata_store = []
self._component = component
def find_non_partial_definition(self, identifier):
"""Returns a non-partial named definition, if it is defined. Otherwise returns None."""
if identifier in self._interfaces:
return self._interfaces[identifier]
if identifier in self._callback_interfaces:
return self._callback_interfaces[identifier]
if identifier in self._namespaces:
return self._namespaces[identifier]
if identifier in self._dictionaries:
return self._dictionaries[identifier]
if identifier in self._enumerations:
return self._enumerations[identifier]
if identifier in self._callback_functions:
return self._callback_functions[identifier]
if identifier in self._typedefs:
return self._typedefs[identifier]
return None
def find_partial_definition(self, identifier):
if identifier in self._partial_interfaces:
return self._partial_interfaces[identifier]
if identifier in self._partial_namespaces:
return self._partial_namespaces[identifier]
if identifier in self._partial_dictionaries:
return self._partial_dictionaries[identifier]
return []
def find_filepath(self, definition):
for metadata in self._metadata_store:
if metadata.definition == definition:
return metadata.filepath
return None
def register_definition(self, definition, filepath):
if type(definition) == Interface:
if definition.is_partial:
self._register_partial_definition(self._partial_interfaces, definition)
else:
self._register_definition(self._interfaces, definition)
elif type(definition) == Namespace:
if definition.is_partial:
self._register_partial_definition(self._partial_namespaces, definition)
else:
self._register_definition(self._namespaces, definition)
elif type(definition) == Dictionary:
if definition.is_partial:
self._register_partial_definition(self._partial_dictionaries, definition)
else:
self._register_definition(self._dictionaries, definition)
elif type(definition) == CallbackInterface:
self._register_definition(self._callback_interfaces, definition)
elif type(definition) == Enumeration:
self._register_definition(self._enumerations, definition)
elif type(definition) == Typedef:
self._register_definition(self._typedefs, definition)
elif type(definition) == CallbackFunction:
self._register_definition(self._callback_functions, definition)
elif type(definition) == Implements:
self._implements.append(definition)
else:
raise ValueError('Unrecognized class definition %s in %s' % (str(type(definition)), filepath))
metadata = _Metadata(definition, filepath)
self._metadata_store.append(metadata)
@property
def interface_identifiers(self):
return self._interfaces.keys()
@property
def callback_interface_identifiers(self):
return self._callback_interfaces.keys()
@property
def namespace_identifiers(self):
return self._namespaces.keys()
@property
def dictionary_identifiers(self):
return self._dictionaries.keys()
@property
def enumeration_identifiers(self):
return self._enumerations.keys()
@property
def callback_function_identifiers(self):
return self._callback_functions.keys()
@property
def typedef_identifiers(self):
return self._typedefs.keys()
@property
def implements(self):
return self._implements
@property
def component(self):
return self._component
def _register_definition(self, definitions, definition):
identifier = definition.identifier
previous_definition = self.find_non_partial_definition(identifier)
if previous_definition and previous_definition == definition:
raise ValueError('Conflict: %s is defined in %s and %s' %
(identifier, self.find_filepath(previous_definition),
self.find_filepath(definition)))
definitions[identifier] = definition
def _register_partial_definition(self, definitions, definition):
identifier = definition.identifier
if identifier not in definitions:
definitions[identifier] = []
definitions[identifier].append(definition)
class _Metadata(object):
"""Metadata holds information of a definition which is not in spec.
- |filepath| shows the .idl file name where the definition is described.
"""
def __init__(self, definition, filepath):
self._definition = definition
self._filepath = filepath
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
from .idl_definition_builder import IdlDefinitionBuilder
from .collection import Collection
# TODO(peria): Merge bindings/scripts/blink_idl_parser.py with tools/idl_parser,
# and put in this directory. Then we can remove this sys.path update.
_SCRIPTS_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), os.pardir)
sys.path.append(_SCRIPTS_PATH)
import blink_idl_parser
class Collector(object):
def __init__(self, component, parser=blink_idl_parser.BlinkIDLParser()):
self._component = component
self._collection = Collection(component)
self._parser = parser
def collect_from_idl_files(self, filepaths):
if type(filepaths) == str:
filepaths = [filepaths]
for filepath in filepaths:
try:
ast = blink_idl_parser.parse_file(self._parser, filepath)
self.collect_from_ast(ast)
except ValueError as ve:
raise ValueError('%s\nin file %s' % (str(ve), filepath))
def collect_from_idl_text(self, text, filename='TEXT'):
ast = self._parser.ParseText(filename, text) # pylint: disable=no-member
self.collect_from_ast(ast)
def collect_from_ast(self, node):
for definition, filepath in IdlDefinitionBuilder.idl_definitions(node):
self._collection.register_definition(definition, filepath)
def get_collection(self):
return self._collection
#!/usr/bin/python
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import unittest
from .collector import Collector
class CollectorTest(unittest.TestCase):
def setUp(self):
self._collector = Collector(component='test')
def collect_from_idl_text(self, idl_text):
self._collector.collect_from_idl_text(idl_text)
return self._collector.get_collection()
def test_definition_filters(self):
idl_text = """
interface MyInterface {};
partial interface MyInterface {};
dictionary MyDictionary {};
dictionary MyDictionary2 {};
partial dictionary MyPartialDictionary {};
namespace MyNamespace {};
partial namespace MyNamespace {};
partial namespace MyNamespace2 {};
partial namespace MyNamespace2 {};
enum MyEnum { "FOO" };
callback MyCallbackFunction = void (DOMString arg);
typedef sequence<Point> Points;
Foo implements Bar;
"""
collection = self.collect_from_idl_text(idl_text)
self.assertEqual(1, len(collection.callback_function_identifiers))
self.assertEqual(1, len(collection.enumeration_identifiers))
self.assertEqual(1, len(collection.interface_identifiers))
self.assertEqual(1, len(collection.namespace_identifiers))
self.assertEqual(1, len(collection.typedef_identifiers))
self.assertEqual(2, len(collection.dictionary_identifiers))
self.assertEqual(1, len(collection.implements))
self.assertEqual(2, len(collection.find_partial_definition('MyNamespace2')))
def test_interface(self):
idl_text = """
interface InterfaceSimpleMembers {
void operation1(DOMString arg);
attribute long longMember;
};
interface InheritInterface : InheritedInterface {};
partial interface PartialInterface {
attribute long longMember;
};
partial interface PartialInterface {
attribute long long longlongMember;
};
"""
collection = self.collect_from_idl_text(idl_text)
interface = collection.find_non_partial_definition('InterfaceSimpleMembers')
self.assertEqual('InterfaceSimpleMembers', interface.identifier)
self.assertEqual(1, len(interface.attributes))
self.assertEqual(1, len(interface.operations))
self.assertEqual('operation1', interface.operations[0].identifier)
self.assertEqual('longMember', interface.attributes[0].identifier)
interface = collection.find_non_partial_definition('InheritInterface')
self.assertEqual('InheritInterface', interface.identifier)
self.assertEqual('InheritedInterface', interface.inherited_interface_name)
partial_interfaces = collection.find_partial_definition('PartialInterface')
self.assertTrue(partial_interfaces[0].is_partial)
self.assertTrue(partial_interfaces[1].is_partial)
attribute = partial_interfaces[0].attributes[0]
self.assertEqual('longMember', attribute.identifier)
attribute = partial_interfaces[1].attributes[0]
self.assertEqual('longlongMember', attribute.identifier)
idl_text = """
interface InterfaceAttributes {
attribute long longAttr;
readonly attribute octet readonlyAttr;
static attribute DOMString staticStringAttr;
attribute [TreatNullAs=EmptyString] DOMString annotatedTypeAttr;
[Unforgeable] attribute DOMString? extendedAttributeAttr;
};
"""
collection = self.collect_from_idl_text(idl_text)
interface = collection.find_non_partial_definition('InterfaceAttributes')
attributes = interface.attributes
self.assertEqual(5, len(attributes))
attribute = attributes[0]
self.assertEqual('longAttr', attribute.identifier)
self.assertEqual('Long', attribute.type.type_name)
attribute = attributes[1]
self.assertEqual('readonlyAttr', attribute.identifier)
self.assertEqual('Octet', attribute.type.type_name)
self.assertTrue(attribute.is_readonly)
attribute = attributes[2]
self.assertEqual('staticStringAttr', attribute.identifier)
self.assertEqual('String', attribute.type.type_name)
self.assertTrue(attribute.is_static)
attribute = attributes[3]
self.assertEqual('annotatedTypeAttr', attribute.identifier)
self.assertEqual('String', attribute.type.type_name)
self.assertEqual('EmptyString', attribute.type.treat_null_as)
attribute = attributes[4]
self.assertEqual('extendedAttributeAttr', attribute.identifier)
self.assertEqual('String', attribute.type.type_name)
self.assertTrue(attribute.type.is_nullable)
self.assertTrue(attribute.extended_attribute_list.has('Unforgeable'))
def test_extended_attributes(self):
idl_text = """
[
NoInterfaceObject,
OriginTrialEnabled=FooBar
] interface ExtendedAttributeInterface {};
"""
collection = self.collect_from_idl_text(idl_text)
interface = collection.find_non_partial_definition('ExtendedAttributeInterface')
extended_attribute_list = interface.extended_attribute_list
self.assertTrue(extended_attribute_list.has('OriginTrialEnabled'))
self.assertTrue(extended_attribute_list.has('NoInterfaceObject'))
self.assertEqual('FooBar', extended_attribute_list.get('OriginTrialEnabled'))
idl_text = """
[
Constructor,
Constructor(DOMString arg),
CustomConstructor,
CustomConstructor(long arg),
NamedConstructor=Audio,
NamedConstructor=Audio(DOMString src)
] interface ConstructorInterface {};
"""
collection = self.collect_from_idl_text(idl_text)
interface = collection.find_non_partial_definition('ConstructorInterface')
constructors = interface.constructors
self.assertEqual(4, len(constructors))
self.assertFalse(constructors[0].is_custom)
self.assertFalse(constructors[1].is_custom)
self.assertTrue(constructors[2].is_custom)
self.assertTrue(constructors[3].is_custom)
named_constructors = interface.named_constructors
self.assertEqual('Audio', named_constructors[0].identifier)
self.assertEqual('Audio', named_constructors[1].identifier)
self.assertEqual('arg', constructors[1].arguments[0].identifier)
self.assertEqual('arg', constructors[3].arguments[0].identifier)
self.assertEqual('src', named_constructors[1].arguments[0].identifier)
idl_text = """
[
Exposed=(Window, Worker),
Exposed(Window Feature1, Worker Feature2)
] interface ExposedInterface {};
"""
collection = self.collect_from_idl_text(idl_text)
interface = collection.find_non_partial_definition('ExposedInterface')
exposures = interface.exposures
self.assertEqual(4, len(exposures))
self.assertEqual('Window', exposures[0].global_interface)
self.assertEqual('Worker', exposures[1].global_interface)
self.assertEqual('Window', exposures[2].global_interface)
self.assertEqual('Worker', exposures[3].global_interface)
self.assertEqual('Feature1', exposures[2].runtime_enabled_feature)
self.assertEqual('Feature2', exposures[3].runtime_enabled_feature)
if __name__ == '__main__':
unittest.main(verbosity=2)
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .idl_types import RecordType
from .idl_types import SequenceType
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-constants
class Constant(object):
_INVALID_IDENTIFIERS = frozenset(['length', 'name', 'prototype'])
_INVALID_TYPES = frozenset([SequenceType, RecordType])
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._type = kwargs.pop('type')
self._value = kwargs.pop('value')
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
if self.identifier in Constant._INVALID_IDENTIFIERS:
raise ValueError('Invalid identifier for a constant: %s' % self.identifier)
if type(self.type) in Constant._INVALID_TYPES:
raise ValueError('sequence<T> must not be used as the type of a constant.')
@property
def identifier(self):
return self._identifier
@property
def type(self):
return self._type
@property
def value(self):
return self._value
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-dictionaries
class Dictionary(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._members = kwargs.pop('members', {})
self._inherited_dictionary_name = kwargs.pop('inherited_dictionary_name', None)
self._is_partial = kwargs.pop('is_partial', False)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def members(self):
return self._members
@property
def inherited_dictionary_name(self):
return self._inherited_dictionary_name
@property
def is_partial(self):
return self._is_partial
@property
def extended_attribute_list(self):
return self._extended_attribute_list
class DictionaryMember(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._type = kwargs.pop('type')
self._default_value = kwargs.pop('default_value', None)
self._is_required = kwargs.pop('is_required', False)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def type(self):
return self._type
@property
def default_value(self):
return self._default_value
@property
def is_required(self):
return self._is_required
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .idl_types import TypeBase
from .utilities import assert_no_extra_args
class EcmaScriptType(TypeBase):
"""
EcmascriptType represents an EcmaScript type, which appears in Chromium IDL files.
@param string type_name : the identifier of a named definition to refer
@param bool is_nullable : True if the type is nullable (optional)
"""
_ALLOWED_TYPE_NAMES = frozenset(['Date'])
def __init__(self, **kwargs):
self._type_name = kwargs.pop('type_name')
self._is_nullable = kwargs.pop('is_nullable', False)
assert_no_extra_args(kwargs)
# Now we use only 'Date' type.
if self.type_name not in EcmaScriptType._ALLOWED_TYPE_NAMES:
raise ValueError('Unknown type name: %s' % self.type_name)
@property
def type_name(self):
return self._type_name
@property
def is_nullable(self):
return self._is_nullable
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-enums
class Enumeration(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._values = kwargs.pop('values', [])
# Extended attributes on enumerations are not allowed in spec, but Blink uses them.
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def values(self):
return self._values
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-extended-attributes
# To work with [Exposed], [Constructor], [CustomConstrucotr], and [NamedConstructor] easily, we define some classes
# for them in this file.
class ExtendedAttributeList(object):
def __init__(self, **kwargs):
self._extended_attributes = kwargs.pop('extended_attributes', {})
self._exposures = tuple(kwargs.pop('exposures', []))
constructors = kwargs.pop('constructors', [])
self._constructors = tuple([ctor for ctor in constructors if type(ctor) == Constructor])
self._named_constructors = tuple([ctor for ctor in constructors if type(ctor) == NamedConstructor])
if self.exposures:
self._extended_attributes['Exposed'] = self.exposures
if self.named_constructors:
self._extended_attributes['NamedConstructor'] = self.named_constructors
if any(ctor.is_custom for ctor in self.constructors):
self._extended_attributes['CustomConstructor'] = [ctor for ctor in self.constructors if ctor.is_custom]
if any(not ctor.is_custom for ctor in self.constructors):
self._extended_attributes['CustomConstructor'] = [ctor for ctor in self.constructors if not ctor.is_custom]
def get(self, key):
return self.extended_attributes.get(key)
def has(self, key):
return key in self.extended_attributes
@property
def extended_attributes(self):
"""
[Exposed], [Constructor], [CustomConstrucotr], and [NamedConstructor] can be taken with
other property methods, but the returned value of this method also includes them.
"""
return self._extended_attributes
@property
def constructors(self):
return self._constructors
@property
def named_constructors(self):
return self._named_constructors
@property
def exposures(self):
return self._exposures
# https://heycam.github.io/webidl/#Constructor
class Constructor(object):
def __init__(self, **kwargs):
self._arguments = kwargs.pop('arguments', [])
self._is_custom = kwargs.pop('is_custom', False)
assert_no_extra_args(kwargs)
@property
def arguments(self):
return self._arguments
@property
def is_custom(self):
return self._is_custom
# https://heycam.github.io/webidl/#NamedConstructor
class NamedConstructor(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier', None)
self._arguments = kwargs.pop('arguments', [])
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def arguments(self):
return self._arguments
# https://heycam.github.io/webidl/#Exposed
class Exposure(object):
"""Exposure holds an exposed target, and can hold a runtime enabled condition.
"[Exposed=global_interface]" is represented as Exposure(global_interface), and
"[Exposed(global_interface runtime_enabled_feature)] is represented as Exposure(global_interface, runtime_enabled_feature).
"""
def __init__(self, **kwargs):
self._global_interface = kwargs.pop('global_interface')
self._runtime_enabled_feature = kwargs.pop('runtime_enabled_feature', None)
assert_no_extra_args(kwargs)
@property
def global_interface(self):
return self._global_interface
@property
def runtime_enabled_feature(self):
return self._runtime_enabled_feature
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from . import literal_token
from .argument import Argument
from .attribute import Attribute
from .callback_function import CallbackFunction
from .callback_interface import CallbackInterface
from .constant import Constant
from .dictionary import Dictionary
from .dictionary import DictionaryMember
from .ecma_script_types import EcmaScriptType
from .enumeration import Enumeration
from .extended_attribute import Constructor
from .extended_attribute import Exposure
from .extended_attribute import ExtendedAttributeList
from .extended_attribute import NamedConstructor
from .idl_types import AnyType
from .idl_types import FrozenArrayType
from .idl_types import ObjectType
from .idl_types import PrimitiveType
from .idl_types import PromiseType
from .idl_types import RecordType
from .idl_types import SequenceType
from .idl_types import StringType
from .idl_types import TypePlaceHolder
from .idl_types import UnionType
from .idl_types import VoidType
from .implements import Implements
from .interface import Interface
from .interface import Iterable
from .interface import Maplike
from .interface import Serializer
from .interface import Setlike
from .literal_token import LiteralToken
from .namespace import Namespace
from .operation import Operation
from .typedef import Typedef
class IdlDefinitionBuilder(object):
@staticmethod
def idl_definitions(node):
"""
This method is a generator that returns an instance of an IDL definition class(*) and the IDL file path |node| refers
on each call. |node| must be a root node of an AST tree, and while the same |node| is used, this method iterates
the AST tree from |node|.
(*) "IDL definition class" means either of Interface, Namespace, Dictionary, Enumeration, Typedef, CallbackFunction,
or Implements classes.
"""
assert node.GetClass() == 'File', (
'Root node of AST must be a File node, but is %s.' % node.GetClass())
filepath = node.GetProperty('FILENAME')
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Interface':
# interface, partial interface, callback interface
interface = IdlDefinitionBuilder.create_interface(child)
yield interface, filepath
elif child_class == 'Namespace':
# namespace, partial namespace
namespace = IdlDefinitionBuilder.create_namespace(child)
yield namespace, filepath
elif child_class == 'Dictionary':
# dictionary, partial dictionary
dictionary = IdlDefinitionBuilder.create_dictionary(child)
yield dictionary, filepath
elif child_class == 'Enum':
enumeration = IdlDefinitionBuilder.create_enumeration(child)
yield enumeration, filepath
elif child_class == 'Typedef':
typedef = IdlDefinitionBuilder.create_typedef(child)
yield typedef, filepath
elif child_class == 'Callback':
callback_function = IdlDefinitionBuilder.create_callback_function(child)
yield callback_function, filepath
elif child_class == 'Implements':
implements = IdlDefinitionBuilder.create_implements(child)
yield implements, filepath
else:
raise ValueError('Unrecognized class definition: %s' % child_class)
@staticmethod
def create_interface(node):
assert node.GetClass() == 'Interface', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
is_callback = bool(node.GetProperty('CALLBACK'))
is_partial = bool(node.GetProperty('PARTIAL'))
attributes = []
operations = []
constants = []
iterable = None
maplike = None
setlike = None
serializer = None
inherited_interface_name = None
extended_attribute_list = None
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Inherit':
inherited_interface_name = child.GetName()
elif child_class == 'Attribute':
attributes.append(IdlDefinitionBuilder.create_attribute(child))
elif child_class == 'Operation':
operations.append(IdlDefinitionBuilder.create_operation(child))
elif child_class == 'Const':
constants.append(IdlDefinitionBuilder.create_constant(child))
elif child_class == 'Stringifier':
stringifier = IdlDefinitionBuilder.create_stringifier(child)
# Stringifier is either of Attribute or Operation.
if type(stringifier) == Operation:
operations.append(stringifier)
elif type(stringifier) == Attribute:
attributes.append(stringifier)
else:
assert False, 'Stringifer must be an attribute or an operation.'
elif child_class == 'Iterable':
iterable = IdlDefinitionBuilder.create_iterable(child)
elif child_class == 'Maplike':
maplike = IdlDefinitionBuilder.create_maplike(child)
elif child_class == 'Setlike':
setlike = IdlDefinitionBuilder.create_setlike(child)
elif child_class == 'Serializer':
serializer = IdlDefinitionBuilder.create_serializer(child)
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
else:
assert False, 'Unrecognized node class: %s' % child_class
if is_callback:
return CallbackInterface(identifier=identifier, attributes=attributes, operations=operations, constants=constants,
inherited_interface_name=inherited_interface_name,
extended_attribute_list=extended_attribute_list)
return Interface(identifier=identifier, attributes=attributes, operations=operations, constants=constants,
iterable=iterable, maplike=maplike, setlike=setlike, serializer=serializer,
inherited_interface_name=inherited_interface_name, is_partial=is_partial,
extended_attribute_list=extended_attribute_list)
@staticmethod
def create_iterable(node):
assert node.GetClass() == 'Iterable', 'Unknown node class: %s' % node.GetClass()
key_type = None
value_type = None
extended_attribute_list = None
type_nodes = []
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
elif child_class == 'Type':
type_nodes.append(child)
else:
assert False, 'Unrecognized node class: %s' % child_class
assert len(type_nodes) in [1, 2], 'iterable<> expects 1 or 2 type parameters, but got %d.' % len(type_nodes)
if len(type_nodes) == 1:
value_type = IdlDefinitionBuilder.create_type(type_nodes[0])
elif len(type_nodes) == 2:
key_type = IdlDefinitionBuilder.create_type(type_nodes[0])
value_type = IdlDefinitionBuilder.create_type(type_nodes[1])
return Iterable(key_type=key_type, value_type=value_type, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_maplike(node):
assert node.GetClass() == 'Maplike', 'Unknown node class: %s' % node.GetClass()
is_readonly = bool(node.GetProperty('READONLY'))
types = []
for child in node.GetChildren():
if child.GetClass() == 'Type':
types.append(child)
else:
raise ValueError('Unrecognized node class: %s' % child.GetClass())
assert len(types) == 2, 'maplike<K, V> requires two type parameters, but got %d.' % len(types)
key_type = IdlDefinitionBuilder.create_type(types[0])
value_type = IdlDefinitionBuilder.create_type(types[1])
return Maplike(key_type=key_type, value_type=value_type, is_readonly=is_readonly)
@staticmethod
def create_setlike(node):
assert node.GetClass() == 'Setlike', 'Unknown node class: %s' % node.GetClass()
is_readonly = bool(node.GetProperty('READONLY'))
children = node.GetChildren()
assert len(children) == 1, 'setlike<T> requires one type parameter, but got %d' % len(children)
value_type = IdlDefinitionBuilder.create_type(children[0])
return Setlike(value_type=value_type, is_readonly=is_readonly)
# BUG(736332): Remove support of legacy serializer.
@staticmethod
def create_serializer(node):
assert node.GetClass() == 'Serializer', 'Unknown node class: %s' % node.GetClass()
is_map = False
has_attribute = False
has_inherit = False
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Map':
is_map = True
has_attribute = bool(child.GetProperty('ATTRIBUTE'))
has_inherit = bool(child.GetProperty('INHERIT'))
else:
assert False, 'Unrecognized node class: %s' % child_class
return Serializer(is_map=is_map, has_attribute=has_attribute, has_inherit=has_inherit)
@staticmethod
def create_namespace(node):
assert node.GetClass() == 'Namespace', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
is_partial = bool(node.GetProperty('PARTIAL'))
attributes = []
operations = []
extended_attribute_list = None
children = node.GetChildren()
for child in children:
child_class = child.GetClass()
if child_class == 'Attribute':
attributes.append(IdlDefinitionBuilder.create_attribute(child))
elif child_class == 'Operation':
operations.append(IdlDefinitionBuilder.create_operation(child))
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
else:
assert False, 'Unrecognized node class: %s' % child_class
return Namespace(identifier=identifier, attributes=attributes, operations=operations,
is_partial=is_partial, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_constant(node):
assert node.GetClass() == 'Const', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
idl_type = None
value = None
extended_attribute_list = None
children = node.GetChildren()
assert len(children) in [2, 3], 'Expected 2 or 3 children for Constant, got %s' % len(children)
idl_type = IdlDefinitionBuilder.create_base_type(children[0])
value_node = children[1]
assert value_node.GetClass() == 'Value', 'Expected Value node, got %s' % value_node.GetClass()
# Regardless of its type, value is stored in string format.
value = value_node.GetProperty('VALUE')
if len(children) == 3:
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(children[2])
return Constant(identifier=identifier, type=idl_type, value=value, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_attribute(node, ext_attrs=None):
assert node.GetClass() == 'Attribute', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
is_static = bool(node.GetProperty('STATIC'))
is_readonly = bool(node.GetProperty('READONLY'))
idl_type = None
extended_attribute_list = ext_attrs
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Type':
idl_type = IdlDefinitionBuilder.create_type(child)
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child, extended_attribute_list)
else:
assert False, 'Unrecognized node class: %s' % child_class
return Attribute(identifier=identifier, type=idl_type, is_static=is_static, is_readonly=is_readonly,
extended_attribute_list=extended_attribute_list)
@staticmethod
def create_operation(node, ext_attrs=None):
assert node.GetClass() == 'Operation', 'Unknown node class: %s' % node.GetClass()
return_type = None
arguments = None
special_keywords = []
is_static = False
extended_attribute_list = ext_attrs
identifier = node.GetName()
is_static = bool(node.GetProperty('STATIC'))
properties = node.GetProperties()
for special_keyword in ['DELETER', 'GETTER', 'LEGACYCALLER', 'SETTER']:
if special_keyword in properties:
special_keywords.append(special_keyword.lower())
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Arguments':
arguments = IdlDefinitionBuilder.create_arguments(child)
elif child_class == 'Type':
return_type = IdlDefinitionBuilder.create_type(child)
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child, extended_attribute_list)
else:
assert False, 'Unrecognized node class: %s' % child_class
assert identifier or special_keywords, 'Regular operations must have an identifier.'
return Operation(identifier=identifier, return_type=return_type, arguments=arguments,
special_keywords=special_keywords, is_static=is_static,
extended_attribute_list=extended_attribute_list)
@staticmethod
def create_stringifier(node):
assert node.GetClass() == 'Stringifier'
# stringifier can be either of an attribute or an operation. This method checks which type we can
# convert the given |node| subtree.
ext_attributes = None
stringifier = None
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'ExtAttributes':
ext_attributes = IdlDefinitionBuilder.create_extended_attribute_list(child)
elif child_class == 'Attribute':
assert stringifier is None
stringifier = IdlDefinitionBuilder.create_attribute(child, ext_attributes)
elif child_class == 'Operation':
assert stringifier is None
stringifier = IdlDefinitionBuilder.create_operation(child, ext_attributes)
if stringifier is None:
# The stringifer keyword is declared in a shorthand style.
# https://heycam.github.io/webidl/#idl-stringifiers
return_type = StringType(string_type='DOMString')
stringifier = Operation(identifier='', return_type=return_type, extended_attribute_list=ext_attributes)
return stringifier
@staticmethod
def create_dictionary(node):
assert node.GetClass() == 'Dictionary', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
is_partial = bool(node.GetProperty('PARTIAL'))
members = {}
inherited_dictionary_name = None
extended_attribute_list = None
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Inherit':
inherited_dictionary_name = child.GetName()
elif child_class == 'Key':
member = IdlDefinitionBuilder.create_dictionary_member(child)
# No duplicates are allowed through inheritances.
assert member.identifier not in members, 'Duplicated dictionary members: %s' % member.identifier
members[member.identifier] = member
elif child.GetClass() == 'ExtAttributes':
# Extended attributes are not applicable to Dictionary in spec, but Blink defines an extended attribute which
# is applicable to Dictionary.
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
else:
assert False, 'Unrecognized node class: %s' % child_class
return Dictionary(identifier=identifier, members=members, inherited_dictionary_name=inherited_dictionary_name,
is_partial=is_partial, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_dictionary_member(node):
assert node.GetClass() == 'Key', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
is_required = bool(node.GetProperty('REQUIRED'))
idl_type = None
default_value = None
extended_attribute_list = None
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Type':
idl_type = IdlDefinitionBuilder.create_type(child)
elif child_class == 'Default':
default_value = IdlDefinitionBuilder.create_literal_token(child)
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
else:
assert False, 'Unrecognized node class: %s' % child_class
return DictionaryMember(identifier=identifier, type=idl_type, default_value=default_value,
is_required=is_required, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_enumeration(node):
assert node.GetClass() == 'Enum', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
values = []
extended_attribute_list = None
for child in node.GetChildren():
if child.GetClass() == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
else:
values.append(child.GetName())
return Enumeration(identifier=identifier, values=values, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_callback_function(node):
assert node.GetClass() == 'Callback', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
return_type = None
arguments = None
extended_attribute_list = None
children = node.GetChildren()
assert len(children) in [2, 3], 'Expected 2 or 3 children, got %s' % len(children)
for child in children:
child_class = child.GetClass()
if child_class == 'Type':
return_type = IdlDefinitionBuilder.create_type(child)
elif child_class == 'Arguments':
arguments = IdlDefinitionBuilder.create_arguments(child)
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
else:
assert False, 'Unknown node: %s' % child_class
return CallbackFunction(identifier=identifier, return_type=return_type, arguments=arguments,
extended_attribute_list=extended_attribute_list)
@staticmethod
def create_typedef(node):
assert node.GetClass() == 'Typedef', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
children = node.GetChildren()
assert len(children) == 1, 'Typedef requires 1 child node, but got %d.' % len(children)
actual_type = IdlDefinitionBuilder.create_type(children[0])
return Typedef(identifier=identifier, type=actual_type)
@staticmethod
def create_implements(node):
assert node.GetClass() == 'Implements', 'Unknown node class: %s' % node.GetClass()
implementer_name = node.GetName()
implementee_name = node.GetProperty('REFERENCE')
return Implements(implementer_name=implementer_name, implementee_name=implementee_name)
@staticmethod
def create_type(node):
assert node.GetClass() == 'Type', 'Expecting Type node, but %s' % node.GetClass()
children = node.GetChildren()
assert len(children) in [1, 2], 'Type node expects 1 or 2 child(ren), got %s.' % len(children)
is_nullable = bool(node.GetProperty('NULLABLE'))
extended_attribute_list = None
if len(children) == 2:
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(children[1])
return IdlDefinitionBuilder.create_base_type(children[0], is_nullable, extended_attribute_list)
@staticmethod
def create_base_type(node, is_nullable=False, extended_attribute_list=None):
node_class = node.GetClass()
if node_class == 'Typeref':
return IdlDefinitionBuilder.create_type_place_holder(node, is_nullable)
if node_class == 'PrimitiveType':
return IdlDefinitionBuilder.create_primitive(node, is_nullable, extended_attribute_list)
if node_class == 'StringType':
return IdlDefinitionBuilder.create_string(node, is_nullable, extended_attribute_list)
if node_class == 'Any':
return IdlDefinitionBuilder.create_any()
if node_class == 'UnionType':
return IdlDefinitionBuilder.create_union(node, is_nullable)
if node_class == 'Promise':
return IdlDefinitionBuilder.create_promise(node)
if node_class == 'Record':
return IdlDefinitionBuilder.create_record(node, is_nullable)
if node_class == 'Sequence':
return IdlDefinitionBuilder.create_sequence(node, is_nullable)
if node_class == 'FrozenArray':
return IdlDefinitionBuilder.create_frozen_array(node, is_nullable)
assert False, 'Unrecognized node class: %s' % node_class
@staticmethod
def create_type_place_holder(node, is_nullable=False):
assert node.GetClass() == 'Typeref', 'Unknown node class: %s' % node.GetClass()
return TypePlaceHolder(identifier=node.GetName(), is_nullable=is_nullable)
@staticmethod
def create_string(node, is_nullable=False, extended_attribute_list=None):
assert node.GetClass() == 'StringType', 'Unknown node class: %s' % node.GetClass()
string_type = node.GetName()
treat_null_as = None
if extended_attribute_list:
treat_null_as = extended_attribute_list.get('TreatNullAs')
return StringType(string_type=string_type, is_nullable=is_nullable, treat_null_as=treat_null_as)
@staticmethod
def create_primitive(node, is_nullable=False, extended_attribute_list=None):
assert node.GetClass() == 'PrimitiveType', 'Unknown node class: %s' % node.GetClass()
type_name = node.GetName()
if type_name == 'object':
return ObjectType(is_nullable=is_nullable)
if type_name == 'void':
return VoidType()
if type_name in ['Date']:
return EcmaScriptType(type_name=type_name, is_nullable=is_nullable)
if node.GetProperty('UNRESTRICTED'):
type_name = 'unrestricted ' + type_name
is_clamp = False
is_enforce_range = False
if extended_attribute_list:
is_clamp = extended_attribute_list.has('Clamp')
is_enforce_range = extended_attribute_list.has('EnforceRange')
return PrimitiveType(name=type_name, is_nullable=is_nullable, is_clamp=is_clamp, is_enforce_range=is_enforce_range)
@staticmethod
def create_any():
return AnyType()
@staticmethod
def create_union(node, is_nullable=False):
assert node.GetClass() == 'UnionType', 'Unknown node class: %s' % node.GetClass()
member_types = [IdlDefinitionBuilder.create_type(child) for child in node.GetChildren()]
return UnionType(member_types=member_types, is_nullable=is_nullable)
@staticmethod
def create_promise(node):
assert node.GetClass() == 'Promise', 'Unknown node class: %s' % node.GetClass()
children = node.GetChildren()
assert len(children) == 1, 'Promise<T> node expects 1 child, got %d' % len(children)
result_type = IdlDefinitionBuilder.create_type(children[0])
return PromiseType(result_type=result_type)
@staticmethod
def create_record(node, is_nullable=False):
assert node.GetClass() == 'Record', 'Unknown node class: %s' % node.GetClass()
children = node.GetChildren()
assert len(children) == 2, 'record<K,V> node expects exactly 2 children, got %d.' % len(children)
key_node = children[0]
assert key_node.GetClass() == 'StringType', 'Key in record<K,V> node must be a string type, got %s.' % key_node.GetClass()
key_type = IdlDefinitionBuilder.create_string(key_node, False, key_node.GetProperty('ExtAttributes'))
value_node = children[1]
assert value_node.GetClass() == 'Type', 'Unrecognized node class for record<K,V> value: %s' % value_node.GetClass()
value_type = IdlDefinitionBuilder.create_type(value_node)
return RecordType(key_type=key_type, value_type=value_type, is_nullable=is_nullable)
@staticmethod
def create_sequence(node, is_nullable=False):
assert node.GetClass() == 'Sequence', 'Unknown node class: %s' % node.GetClass()
children = node.GetChildren()
assert len(children) == 1, 'sequence<T> node expects exactly 1 child, got %d' % len(children)
element_type = IdlDefinitionBuilder.create_type(children[0])
return SequenceType(element_type=element_type, is_nullable=is_nullable)
@staticmethod
def create_frozen_array(node, is_nullable=False):
assert node.GetClass() == 'FrozenArray', 'Unknown node class: %s' % node.GetClass()
children = node.GetChildren()
assert len(children) == 1, 'FrozenArray<T> node expects exactly 1 child, got %d' % len(children)
element_type = IdlDefinitionBuilder.create_type(children[0])
return FrozenArrayType(element_type=element_type, is_nullable=is_nullable)
@staticmethod
def create_argument(node):
assert node.GetClass() == 'Argument', 'Unknown node class: %s' % node.GetClass()
identifier = node.GetName()
is_optional = node.GetProperty('OPTIONAL')
idl_type = None
is_variadic = False
default_value = None
extended_attribute_list = None
for child in node.GetChildren():
child_class = child.GetClass()
if child_class == 'Type':
idl_type = IdlDefinitionBuilder.create_type(child)
elif child_class == 'ExtAttributes':
extended_attribute_list = IdlDefinitionBuilder.create_extended_attribute_list(child)
elif child_class == 'Argument':
child_name = child.GetName()
assert child_name == '...', 'Unrecognized Argument node; expected "...", got "%s"' % child_name
is_variadic = bool(child.GetProperty('ELLIPSIS'))
elif child_class == 'Default':
default_value = IdlDefinitionBuilder.create_literal_token(child)
else:
assert False, 'Unrecognized node class: %s' % child_class
return Argument(identifier=identifier, type=idl_type, is_optional=is_optional, is_variadic=is_variadic,
default_value=default_value, extended_attribute_list=extended_attribute_list)
@staticmethod
def create_arguments(node):
assert node.GetClass() == 'Arguments', 'Unknown node class: %s' % node.GetClass()
return [IdlDefinitionBuilder.create_argument(child) for child in node.GetChildren()]
@staticmethod
def create_extended_attribute_list(node, existing_list=None):
"""
Returns a dict for extended attributes. For most cases, keys and values are strings, but we have following exceptions:
'Constructors': value is a list of Constructor's.
'Exposures': value is a list of Exposed's.
"""
assert node.GetClass() == 'ExtAttributes', 'Unknown node class: %s' % node.GetClass()
extended_attributes = {}
constructors = []
exposures = []
for child in node.GetChildren():
name = child.GetName()
if name in ('Constructor', 'CustomConstructor', 'NamedConstructor'):
constructors.append(IdlDefinitionBuilder.create_constructor(child))
elif name == 'Exposed':
exposures.extend(IdlDefinitionBuilder.create_exposures(child))
else:
value = child.GetProperty('VALUE')
extended_attributes[name] = value
if existing_list:
constructors = constructors + list(existing_list.constructors)
exposures = exposures + list(existing_list.exposures)
extended_attributes = extended_attributes.update(existing_list.extended_attributes)
return ExtendedAttributeList(extended_attributes=extended_attributes, constructors=constructors, exposures=exposures)
@staticmethod
def create_constructor(node):
assert node.GetClass() == 'ExtAttribute' and node.GetName() in ('Constructor', 'CustomConstructor', 'NamedConstructor')
identifier = node.GetProperty('VALUE', None)
arguments = []
children = node.GetChildren()
constructor_type = node.GetName()
if len(children) > 0:
child = children[0]
if child.GetClass() == 'Arguments':
arguments = IdlDefinitionBuilder.create_arguments(child)
elif child.GetClass() == 'Call':
arguments = IdlDefinitionBuilder.create_arguments(child.GetChildren()[0])
identifier = child.GetName()
else:
assert False, 'Unexpected class: %s' % child.GetClass()
assert constructor_type in ('Constructor', 'CustomConstructor', 'NamedConstructor'), \
'Unknown constructor type: %s' % constructor_type
if constructor_type == 'NamedConstructor':
return NamedConstructor(identifier=identifier, arguments=arguments)
is_custom = (constructor_type == 'CustomConstructor')
return Constructor(arguments=arguments, is_custom=is_custom)
@staticmethod
def create_exposures(node):
assert node.GetClass() == 'ExtAttribute' and node.GetName() == 'Exposed'
value = node.GetProperty('VALUE')
if value:
# No runtime enabled feature flags
if isinstance(value, list):
return [Exposure(global_interface=v) for v in value]
else:
return [Exposure(global_interface=value)]
arguments = IdlDefinitionBuilder.create_arguments(node.GetChildren()[0])
exposures = []
for arg in arguments:
idl_type = arg.type
assert type(idl_type) == TypePlaceHolder, 'Invalid arguments in Exposed'
exposures.append(Exposure(global_interface=idl_type.type_name, runtime_enabled_feature=arg.identifier))
return exposures
@staticmethod
def create_literal_token(node):
assert node.GetClass() in ('Literal', 'Default')
type_name = node.GetProperty('TYPE')
value = node.GetProperty('VALUE')
if type_name == 'integer':
value = int(value, base=0)
elif type_name == 'float':
value = float(value)
elif type_name == 'NULL':
return literal_token.NULL_TOKEN
else:
assert type_name in ['DOMString', 'boolean', 'sequence'], 'Unrecognized type: %s' % type_name
return LiteralToken(type_name=type_name, value=value)
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .utilities import assert_no_extra_args
# Details of each type is described in
# https://heycam.github.io/webidl/#idl-types
class TypeBase(object):
"""
TypeBase is a base class for all classes for IDL types.
"""
@property
def type_name(self):
assert False, 'type_name() is not implemented'
class AnyType(TypeBase):
@property
def type_name(self):
return 'Any'
class PrimitiveType(TypeBase):
"""
PrimitiveType represents either of integer types, float types, or 'boolean'.
* integer types: byte, octet, (unsigned) short, (unsigned) long, (unsigned) long long
* float types: (unrestricted) float, (unrestricted) double
@param string name : the name of a primitive type
@param bool is_nullable : True if the type is nullable (optional)
@param bool is_clamp : True if the type has [Clamp] annotation (optional)
@param bool is_enforce_range : True if the type has [EnforceRange] annotation (optional)
"""
_INTEGER_TYPES = frozenset([
'byte', 'octet', 'short', 'unsigned short', 'long', 'unsigned long', 'long long', 'unsigned long long'
])
_FLOAT_TYPES = frozenset([
'float', 'unrestricted float', 'double', 'unrestricted double'
])
_PRIMITIVE_TYPES = _INTEGER_TYPES | _FLOAT_TYPES | frozenset(['boolean'])
def __init__(self, **kwargs):
self._name = kwargs.pop('name')
self._is_nullable = kwargs.pop('is_nullable', False)
self._is_clamp = kwargs.pop('is_clamp', False)
self._is_enforce_range = kwargs.pop('is_enforce_range', False)
assert_no_extra_args(kwargs)
if self._name not in PrimitiveType._PRIMITIVE_TYPES:
raise ValueError('Unknown type name: %s' % self._name)
if self.is_clamp and self.is_enforce_range:
raise ValueError('[Clamp] and [EnforceRange] cannot be associated together')
if (self.is_clamp or self.is_enforce_range) and not self.is_integer_type:
raise ValueError('[Clamp] or [EnforceRange] cannot be associated with %s' % self._name)
@property
def type_name(self):
return ''.join([word.capitalize() for word in self._name.split(' ')])
@property
def is_nullable(self):
return self._is_nullable
@property
def is_clamp(self):
return self._is_clamp
@property
def is_enforce_range(self):
return self._is_enforce_range
@property
def is_integer_type(self):
return self._name in PrimitiveType._INTEGER_TYPES
@property
def is_float_type(self):
return self._name in PrimitiveType._FLOAT_TYPES
@property
def is_numeric_type(self):
return self.is_integer_type or self.is_float_type
class StringType(TypeBase):
"""
StringType represents a string type.
@param StringType.Type string_type : a type of string
@param bool is_nullable : True if the string is nullable (optional)
@param StringType.TreatNullAs treat_null_as : argument of an extended attribute [TreatNullAs] (optional)
"""
STRING_TYPES = ('DOMString', 'ByteString', 'USVString')
TREAT_NULL_AS = ('EmptyString', 'NullString')
def __init__(self, **kwargs):
self._string_type = kwargs.pop('string_type')
self._is_nullable = kwargs.pop('is_nullable', False)
self._treat_null_as = kwargs.pop('treat_null_as', None)
assert_no_extra_args(kwargs)
if self._string_type not in StringType.STRING_TYPES:
raise ValueError('Unknown string type: %s' % self._string_type)
if self.treat_null_as and self.treat_null_as not in StringType.TREAT_NULL_AS:
raise ValueError('Unknown TreatAsNull parameter: %s' % self.treat_null_as)
@property
def type_name(self):
if self._string_type == 'DOMString':
return 'String'
return self._string_type
@property
def is_nullable(self):
return self._is_nullable
@property
def treat_null_as(self):
return self._treat_null_as
class ObjectType(TypeBase):
"""
ObjectType represents 'object' type in Web IDL spec.
@param bool is_nullable : True if the type is nullable (optional)
"""
def __init__(self, **kwargs):
self._is_nullable = kwargs.pop('is_nullable', False)
assert_no_extra_args(kwargs)
@property
def type_name(self):
return 'Object'
@property
def is_nullable(self):
return self._is_nullable
class SequenceType(TypeBase):
"""
SequenceType represents a sequence type 'sequence<T>' in Web IDL spec.
@param TypeBase element_type : Type of element T
@param bool is_nullable : True if the type is nullable (optional)
"""
def __init__(self, **kwargs):
self._element_type = kwargs.pop('element_type')
self._is_nullable = kwargs.pop('is_nullable', False)
assert_no_extra_args(kwargs)
if not isinstance(self.element_type, TypeBase):
raise ValueError('element_type must be an instance of TypeBase inheritances')
@property
def type_name(self):
return self.element_type.type_name + 'Sequence'
@property
def is_nullable(self):
return self._is_nullable
@property
def element_type(self):
return self._element_type
class RecordType(TypeBase):
"""
RecordType represents a record type 'record<K, V>' in Web IDL spec.
@param StringType key_type : Type of key K
@param TypeBase value_type : Type of value V
@param bool is_nullable : True if the record type is nullable (optional)
"""
def __init__(self, **kwargs):
self._key_type = kwargs.pop('key_type')
self._value_type = kwargs.pop('value_type')
self._is_nullable = kwargs.pop('is_nullable', False)
assert_no_extra_args(kwargs)
if type(self.key_type) != StringType:
raise ValueError('key_type parameter must be an instance of StringType.')
if not isinstance(self.value_type, TypeBase):
raise ValueError('value_type parameter must be an instance of TypeBase inheritances.')
@property
def type_name(self):
return self.key_type.type_name + self.value_type.type_name + 'Record'
@property
def key_type(self):
return self._key_type
@property
def value_type(self):
return self._value_type
@property
def is_nullable(self):
return self._is_nullable
class PromiseType(TypeBase):
"""
PromiseType represents a promise type 'promise<T>' in Web IDL spec.
@param TypeBase result_type : Type of the promise's result V
"""
def __init__(self, **kwargs):
self._result_type = kwargs.pop('result_type')
assert_no_extra_args(kwargs)
@property
def type_name(self):
return self.result_type.type_name + 'Promise'
@property
def result_type(self):
return self._result_type
class UnionType(TypeBase):
"""
UnionType represents a union type in Web IDL spec.
@param [TypeBase] member_types : List of member types
@param bool is_nullable : True if the type is nullable (optional)
"""
def __init__(self, **kwargs):
def count_nullable_member_types():
number = 0
for member in self.member_types:
if type(member) == UnionType:
number = number + member.number_of_nullable_member_types
elif type(member) not in (AnyType, PromiseType) and member.is_nullable:
number = number + 1
return number
self._member_types = tuple(kwargs.pop('member_types'))
self._is_nullable = kwargs.pop('is_nullable', False)
self._number_of_nullable_member_types = count_nullable_member_types() # pylint: disable=invalid-name
assert_no_extra_args(kwargs)
if len(self.member_types) < 2:
raise ValueError('Union type must have 2 or more member types, but got %d.' % len(self.member_types))
if any(type(member) == AnyType for member in self.member_types):
raise ValueError('any type must not be used as a union member type.')
if self.number_of_nullable_member_types > 1:
raise ValueError('The number of nullable member types of a union type must be 0 or 1, but %d' %
self.number_of_nullable_member_types)
@property
def type_name(self):
return 'Or'.join(member.type_name for member in self.member_types)
@property
def member_types(self):
return self._member_types
@property
def is_nullable(self):
return self._is_nullable
@property
def number_of_nullable_member_types(self):
return self._number_of_nullable_member_types
@property
def flattened_member_types(self):
flattened = set()
# TODO(peria): In spec, we have to remove type annotations and nullable flags.
for member in self.member_types:
if type(member) != UnionType:
flattened.add(member)
else:
flattened.update(member.flattened_member_types)
return flattened
class FrozenArrayType(TypeBase):
"""
FrozenArrayType represents a frozen array type 'FrozenArray<T>' in Web IDL.
@param TypeBase element_type : Type of element T
@param bool is_nullable : True if the type is nullable (optional)
"""
def __init__(self, **kwargs):
self._element_type = kwargs.pop('element_type')
self._is_nullable = kwargs.pop('is_nullable', False)
assert_no_extra_args(kwargs)
@property
def type_name(self):
return self.element_type.type_name + 'Array'
@property
def element_type(self):
return self._element_type
@property
def is_nullable(self):
return self._is_nullable
class VoidType(TypeBase):
@property
def type_name(self):
return 'Void'
class TypePlaceHolder(TypeBase):
"""
TypePlaceHolder is a pseudo type as a place holder of types which use identifers;
interface types, dictionary types, enumeration types, and callback function types.
Because it is not guaranteed that we have a definition of target defintions when
we meet the type used, we use this class as a place holder.
All place holders will be replaced with references after all the defintions in
all components are collected.
@param string identifier : the identifier of a named definition to refer
@param bool is_nullable : True if the type is nullable (optional)
"""
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._is_nullable = kwargs.pop('is_nullable', False)
assert_no_extra_args(kwargs)
@property
def type_name(self):
return self._identifier
@property
def is_nullable(self):
return self._is_nullable
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-implements-statements
class Implements(object):
"""Implement class represents Implements statement in Web IDL spec."""
def __init__(self, **kwargs):
self._implementer_name = kwargs.pop('implementer_name')
self._implementee_name = kwargs.pop('implementee_name')
assert_no_extra_args(kwargs)
if self.implementer_name == self.implementee_name:
raise ValueError('Implements cannot refer same identifiers: %s' % self.implementer_name)
@property
def implementer_name(self):
return self._implementer_name
@property
def implementee_name(self):
return self._implementee_name
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-interfaces
class Interface(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._attributes = tuple(kwargs.pop('attributes', []))
self._operations = tuple(kwargs.pop('operations', []))
self._constants = tuple(kwargs.pop('constants', []))
self._iterable = kwargs.pop('iterable', None)
self._maplike = kwargs.pop('maplike', None)
self._setlike = kwargs.pop('setlike', None)
# BUG(736332): Remove support of legacy serializer members.
self._serializer = kwargs.pop('serializer', None)
self._inherited_interface_name = kwargs.pop('inherited_interface_name', None)
self._is_partial = kwargs.pop('is_partial', False)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
num_declaration = (1 if self.iterable else 0) + (1 if self.maplike else 0) + (1 if self.setlike else 0)
if num_declaration > 1:
raise ValueError('At most one of iterable<>, maplike<>, or setlike<> must be applied.')
@property
def identifier(self):
return self._identifier
@property
def attributes(self):
return self._attributes
@property
def operations(self):
return self._operations
@property
def constants(self):
return self._constants
@property
def iterable(self):
return self._iterable
@property
def maplike(self):
return self._maplike
@property
def setlike(self):
return self._setlike
@property
def serializer(self):
return self._serializer
@property
def inherited_interface_name(self):
return self._inherited_interface_name
@property
def is_partial(self):
return self._is_partial
@property
def constructors(self):
return self.extended_attribute_list.constructors
@property
def named_constructors(self):
return self.extended_attribute_list.named_constructors
@property
def exposures(self):
return self.extended_attribute_list.exposures
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# https://heycam.github.io/webidl/#idl-iterable
class Iterable(object):
def __init__(self, **kwargs):
self._key_type = kwargs.pop('key_type', None)
self._value_type = kwargs.pop('value_type')
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def key_type(self):
return self._key_type
@property
def value_type(self):
return self._value_type
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# https://heycam.github.io/webidl/#idl-maplike
class Maplike(object):
def __init__(self, **kwargs):
self._key_type = kwargs.pop('key_type')
self._value_type = kwargs.pop('value_type')
self._is_readonly = kwargs.pop('is_readonly', False)
assert_no_extra_args(kwargs)
@property
def key_type(self):
return self._key_type
@property
def value_type(self):
return self._value_type
@property
def is_readonly(self):
return self._is_readonly
# https://heycam.github.io/webidl/#idl-setlike
class Setlike(object):
def __init__(self, **kwargs):
self._value_type = kwargs.pop('value_type')
self._is_readonly = kwargs.pop('is_readonly', False)
assert_no_extra_args(kwargs)
@property
def value_type(self):
return self._value_type
@property
def is_readonly(self):
return self._is_readonly
# https://www.w3.org/TR/WebIDL-1/#idl-serializers
# BUG(736332): Remove support of legacy serializer.
# We support styles only used in production code. i.e.
# - serializer;
# - serializer = { attribute };
# - serializer = { inherit, attribute };
class Serializer(object):
def __init__(self, **kwargs):
self._is_map = kwargs.pop('is_map', False)
self._has_attribute = kwargs.pop('has_attribute', False)
self._has_inherit = kwargs.pop('has_inherit', False)
assert_no_extra_args(kwargs)
if (self.has_attribute or self.has_inherit) and not self._is_map:
raise ValueError('has_attribute and has_inherit must be set with is_map')
@property
def is_map(self):
return self._is_map
@property
def has_attribute(self):
return self._has_attribute
@property
def has_inherit(self):
return self._has_inherit
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .utilities import assert_no_extra_args
class LiteralToken(object):
"""Literal class represents literal tokens in Web IDL. It appears
- default values of dictionary members
- default values of arguments in operations
- constant values in interfaces (string and [] are not allowed)
- arguments of some extended attributes
"""
def __init__(self, **kwargs):
self._type_name = kwargs.pop('type_name')
self._value = kwargs.pop('value')
assert_no_extra_args(kwargs)
@property
def type_name(self):
return self._type_name
@property
def value(self):
return self._value
NULL_TOKEN = LiteralToken(type_name='NULL', value='null')
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-namespaces
class Namespace(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._attributes = tuple(kwargs.pop('attributes', []))
self._operations = tuple(kwargs.pop('operations', []))
self._is_partial = kwargs.pop('is_partial', False)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def attributes(self):
return self._attributes
@property
def operations(self):
return self._operations
@property
def exposures(self):
return self.extended_attribute_list.exposures
@property
def is_partial(self):
return self._is_partial
@property
def extended_attribute_list(self):
return self._extended_attribute_list
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .extended_attribute import ExtendedAttributeList
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-operations
class Operation(object):
# https://www.w3.org/TR/WebIDL-1/#idl-special-operations
_SPECIAL_KEYWORDS = frozenset(['deleter', 'getter', 'legacycaller', 'setter', 'stringifier', 'serializer'])
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._return_type = kwargs.pop('return_type')
self._arguments = tuple(kwargs.pop('arguments', []))
self._special_keywords = frozenset(kwargs.pop('special_keywords', []))
self._is_static = kwargs.pop('is_static', False)
self._extended_attribute_list = kwargs.pop('extended_attribute_list', ExtendedAttributeList())
assert_no_extra_args(kwargs)
if any(keyword not in Operation._SPECIAL_KEYWORDS for keyword in self._special_keywords):
raise ValueError('Unknown keyword is specified in special keywords')
@property
def identifier(self):
return self._identifier
@property
def return_type(self):
return self._return_type
@property
def arguments(self):
return self._arguments
@property
def special_keywords(self):
return self._special_keywords
@property
def is_static(self):
return self._is_static
@property
def extended_attribute_list(self):
return self._extended_attribute_list
@property
def is_regular(self):
return self.identifier and not self.is_static
@property
def is_special(self):
return bool(self.special_keywords)
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from .utilities import assert_no_extra_args
# https://heycam.github.io/webidl/#idl-typedefs
class Typedef(object):
def __init__(self, **kwargs):
self._identifier = kwargs.pop('identifier')
self._type = kwargs.pop('type')
assert_no_extra_args(kwargs)
@property
def identifier(self):
return self._identifier
@property
def type(self):
return self._type
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
def assert_no_extra_args(kwargs):
if kwargs:
raise ValueError('Unknown parameters are passed: %s' % kwargs.keys())
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment