0

Enable python formatting in //tools/json_schema_compiler/

Python formatting using `git cl format` has been enabled in the codebase
for a while now by including a .style.yapf file in your folder
hierarchy. However back in 2020 an exception was added to ignore the
json schema compiler folder, as the code in there uses a lot of chained
function calls that are split across lines for readability and the
formatter was condensing them into a much less readable state.

This CL resolves this by using the line continuation character `\` for
these chained function lines, allowing us to retain the more readable
indentation and also enable the formatter by default. It also runs a
full format over all the files in the directory, to get them into a
consistent state where we can have the formatter enabled by default
going forward with low impact.

No behavior change is expected.

Bug: 40711753
Change-Id: I6e10dc5af022ce0e3557099a84773aa9cc92d2e4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5804254
Commit-Queue: Tim <tjudkins@chromium.org>
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1345613}
This commit is contained in:
Tim Judkins
2024-08-22 20:16:07 +00:00
committed by Chromium LUCI CQ
parent 91e2dd0507
commit 1e3d052650
43 changed files with 2267 additions and 2087 deletions

@@ -5,6 +5,3 @@
third_party/blink/tools/blinkpy/third_party/* third_party/blink/tools/blinkpy/third_party/*
third_party/blink/web_tests/external/wpt/* third_party/blink/web_tests/external/wpt/*
tools/valgrind/asan/third_party/asan_symbolize.py tools/valgrind/asan/third_party/asan_symbolize.py
# TODO(crbug.com/1116155): Enable this for formatting by yapf.
tools/json_schema_compiler/*

File diff suppressed because it is too large Load Diff

@@ -2,19 +2,22 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
class Code(object): class Code(object):
"""A convenience object for constructing code. """A convenience object for constructing code.
Logically each object should be a block of code. All methods except |Render| Logically each object should be a block of code. All methods except |Render|
and |IsEmpty| return self. and |IsEmpty| return self.
""" """
def __init__(self, indent_size=2, comment_length=80): def __init__(self, indent_size=2, comment_length=80):
self._code = [] self._code = []
self._indent_size = indent_size self._indent_size = indent_size
self._comment_length = comment_length self._comment_length = comment_length
self._line_prefixes = [] self._line_prefixes = []
def Append(self, line='', def Append(self,
line='',
substitute=True, substitute=True,
indent_level=None, indent_level=None,
new_line=True, new_line=True,
@@ -110,8 +113,11 @@ class Code(object):
self.Append(line) self.Append(line)
return self return self
def Comment(self, comment, comment_prefix='// ', def Comment(self,
wrap_indent=0, new_line=True): comment,
comment_prefix='// ',
wrap_indent=0,
new_line=True):
"""Adds the given string as a comment. """Adds the given string as a comment.
Will split the comment if it's too long. Use mainly for variable length Will split the comment if it's too long. Use mainly for variable length
@@ -119,6 +125,7 @@ class Code(object):
Unaffected by code.Substitute(). Unaffected by code.Substitute().
""" """
# Helper function to trim a comment to the maximum length, and return one # Helper function to trim a comment to the maximum length, and return one
# line and the remainder of the comment. # line and the remainder of the comment.
def trim_comment(comment, max_len): def trim_comment(comment, max_len):
@@ -196,6 +203,7 @@ class Code(object):
class Line(object): class Line(object):
"""A line of code. """A line of code.
""" """
def __init__(self, value, substitute=True): def __init__(self, value, substitute=True):
self.value = value self.value = value
self.substitute = substitute self.substitute = substitute

@@ -6,7 +6,9 @@
from code_util import Code from code_util import Code
import unittest import unittest
class CodeTest(unittest.TestCase): class CodeTest(unittest.TestCase):
def testAppend(self): def testAppend(self):
c = Code() c = Code()
c.Append('line') c.Append('line')
@@ -14,61 +16,56 @@ class CodeTest(unittest.TestCase):
def testBlock(self): def testBlock(self):
c = Code() c = Code()
(c.Append('line') (c.Append('line') \
.Sblock('sblock') .Sblock('sblock') \
.Append('inner') .Append('inner') \
.Append('moreinner') .Append('moreinner') \
.Sblock('moresblock') .Sblock('moresblock') \
.Append('inner') .Append('inner') \
.Eblock('out') .Eblock('out') \
.Append('inner') .Append('inner') \
.Eblock('out') .Eblock('out')
) )
self.assertEqual( self.assertEqual(
'line\n' 'line\n'
'sblock\n' 'sblock\n'
' inner\n' ' inner\n'
' moreinner\n' ' moreinner\n'
' moresblock\n' ' moresblock\n'
' inner\n' ' inner\n'
' out\n' ' out\n'
' inner\n' ' inner\n'
'out', 'out', c.Render())
c.Render())
def testConcat(self): def testConcat(self):
b = Code() b = Code()
(b.Sblock('2') (b.Sblock('2') \
.Append('2') .Append('2') \
.Eblock('2') .Eblock('2')
) )
c = Code() c = Code()
(c.Sblock('1') (c.Sblock('1') \
.Concat(b) .Concat(b) \
.Append('1') .Append('1') \
.Eblock('1') .Eblock('1')
) )
self.assertMultiLineEqual( self.assertMultiLineEqual('1\n'
'1\n' ' 2\n'
' 2\n' ' 2\n'
' 2\n' ' 2\n'
' 2\n' ' 1\n'
' 1\n' '1', c.Render())
'1',
c.Render())
d = Code() d = Code()
a = Code() a = Code()
a.Concat(d) a.Concat(d)
self.assertEqual('', a.Render()) self.assertEqual('', a.Render())
a.Concat(c) a.Concat(c)
self.assertEqual( self.assertEqual('1\n'
'1\n' ' 2\n'
' 2\n' ' 2\n'
' 2\n' ' 2\n'
' 2\n' ' 1\n'
' 1\n' '1', a.Render())
'1',
a.Render())
def testConcatErrors(self): def testConcatErrors(self):
c = Code() c = Code()
@@ -89,11 +86,9 @@ class CodeTest(unittest.TestCase):
c.Append('%(var1)s %(var2)s %(var3)s') c.Append('%(var1)s %(var2)s %(var3)s')
c.Append('%(var2)s %(var1)s %(var3)s') c.Append('%(var2)s %(var1)s %(var3)s')
c.Substitute({'var1': 'one', 'var2': 'two', 'var3': 'three'}) c.Substitute({'var1': 'one', 'var2': 'two', 'var3': 'three'})
self.assertEqual( self.assertEqual('one two one\n'
'one two one\n' 'one two three\n'
'one two three\n' 'two one three', c.Render())
'two one three',
c.Render())
def testSubstituteErrors(self): def testSubstituteErrors(self):
# No unnamed placeholders allowed when substitute is run # No unnamed placeholders allowed when substitute is run
@@ -118,14 +113,13 @@ class CodeTest(unittest.TestCase):
def testComment(self): def testComment(self):
long_comment = ('This comment is ninety one characters in longness, ' long_comment = ('This comment is ninety one characters in longness, '
'that is, using a different word, length.') 'that is, using a different word, length.')
c = Code() c = Code()
c.Comment(long_comment) c.Comment(long_comment)
self.assertEqual( self.assertEqual(
'// This comment is ninety one characters ' '// This comment is ninety one characters '
'in longness, that is, using a different\n' 'in longness, that is, using a different\n'
'// word, length.', '// word, length.', c.Render())
c.Render())
c = Code() c = Code()
c.Sblock('sblock') c.Sblock('sblock')
c.Comment(long_comment) c.Comment(long_comment)
@@ -139,27 +133,26 @@ class CodeTest(unittest.TestCase):
'eblock\n' 'eblock\n'
'// This comment is ninety one characters in ' '// This comment is ninety one characters in '
'longness, that is, using a different\n' 'longness, that is, using a different\n'
'// word, length.', '// word, length.', c.Render())
c.Render())
# Words that cannot be broken up are left as too long. # Words that cannot be broken up are left as too long.
long_word = 'x' * 100 long_word = 'x' * 100
c = Code() c = Code()
c.Comment('xxx') c.Comment('xxx')
c.Comment(long_word) c.Comment(long_word)
c.Comment('xxx') c.Comment('xxx')
self.assertEqual( self.assertEqual('// xxx\n'
'// xxx\n' '// ' + 'x' * 100 + '\n'
'// ' + 'x' * 100 + '\n' '// xxx', c.Render())
'// xxx',
c.Render())
c = Code(indent_size=2, comment_length=40) c = Code(indent_size=2, comment_length=40)
c.Comment('Pretend this is a Closure Compiler style comment, which should ' c.Comment(
'both wrap and indent', comment_prefix=' * ', wrap_indent=4) 'Pretend this is a Closure Compiler style comment, which should '
'both wrap and indent',
comment_prefix=' * ',
wrap_indent=4)
self.assertEqual( self.assertEqual(
' * Pretend this is a Closure Compiler\n' ' * Pretend this is a Closure Compiler\n'
' * style comment, which should both\n' ' * style comment, which should both\n'
' * wrap and indent', ' * wrap and indent', c.Render())
c.Render())
def testCommentWithSpecialCharacters(self): def testCommentWithSpecialCharacters(self):
c = Code() c = Code()
@@ -170,8 +163,7 @@ class CodeTest(unittest.TestCase):
d.Append('90') d.Append('90')
d.Concat(c) d.Concat(c)
self.assertEqual('90\n' self.assertEqual('90\n'
'// 20% of 80%s', '// 20% of 80%s', d.Render())
d.Render())
def testLinePrefixes(self): def testLinePrefixes(self):
c = Code() c = Code()
@@ -192,8 +184,7 @@ class CodeTest(unittest.TestCase):
' * x: y\n' ' * x: y\n'
' * }\n' ' * }\n'
' * }}\n' ' * }}\n'
' */', ' */', output)
output)
def testSameLineAppendConcatComment(self): def testSameLineAppendConcatComment(self):
c = Code() c = Code()
@@ -206,12 +197,13 @@ class CodeTest(unittest.TestCase):
c = Code() c = Code()
c.Append('This is a') c.Append('This is a')
c.Comment(' spectacular 80-character line thingy ' + c.Comment(' spectacular 80-character line thingy ' +
'that fits wonderfully everywhere.', 'that fits wonderfully everywhere.',
comment_prefix='', comment_prefix='',
new_line=False) new_line=False)
self.assertEqual('This is a spectacular 80-character line thingy that ' + self.assertEqual(
'fits wonderfully everywhere.', 'This is a spectacular 80-character line thingy that ' +
c.Render()) 'fits wonderfully everywhere.', c.Render())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@@ -101,12 +101,10 @@ def GenerateSchema(
# Construct the type generator with all the namespaces in this model. # Construct the type generator with all the namespaces in this model.
schema_dir = os.path.dirname(os.path.relpath(file_paths[0], root)) schema_dir = os.path.dirname(os.path.relpath(file_paths[0], root))
namespace_resolver = NamespaceResolver( namespace_resolver = NamespaceResolver(root, schema_dir, include_rules,
root, schema_dir, include_rules, cpp_namespace_pattern cpp_namespace_pattern)
) type_generator = CppTypeGenerator(api_model, namespace_resolver,
type_generator = CppTypeGenerator( default_namespace)
api_model, namespace_resolver, default_namespace
)
if generator_name in ('cpp-bundle-registration', 'cpp-bundle-schema'): if generator_name in ('cpp-bundle-registration', 'cpp-bundle-schema'):
cpp_bundle_generator = CppBundleGenerator( cpp_bundle_generator = CppBundleGenerator(
root, root,
@@ -187,12 +185,11 @@ if __name__ == '__main__':
default='.', default='.',
help=( help=(
'logical include root directory. Path to schema files from specified' 'logical include root directory. Path to schema files from specified'
' dir will be the include path.' ' dir will be the include path.'),
),
)
parser.add_option(
'-d', '--destdir', help='root directory to output generated files.'
) )
parser.add_option('-d',
'--destdir',
help='root directory to output generated files.')
parser.add_option( parser.add_option(
'-n', '-n',
'--namespace', '--namespace',
@@ -203,11 +200,9 @@ if __name__ == '__main__':
'-b', '-b',
'--bundle-name', '--bundle-name',
default='', default='',
help=( help=('A string to prepend to generated bundle class names, so that '
'A string to prepend to generated bundle class names, so that ' 'multiple bundle rules can be used without conflicting. '
'multiple bundle rules can be used without conflicting. ' 'Only used with one of the cpp-bundle generators.'),
'Only used with one of the cpp-bundle generators.'
),
) )
parser.add_option( parser.add_option(
'-g', '-g',
@@ -216,9 +211,7 @@ if __name__ == '__main__':
choices=GENERATORS, choices=GENERATORS,
help=( help=(
'The generator to use to build the output code. Supported values are' 'The generator to use to build the output code. Supported values are'
' %s' ' %s') % GENERATORS,
)
% GENERATORS,
) )
parser.add_option( parser.add_option(
'-i', '-i',
@@ -229,11 +222,9 @@ if __name__ == '__main__':
parser.add_option( parser.add_option(
'-I', '-I',
'--include-rules', '--include-rules',
help=( help=('A list of paths to include when searching for referenced objects,'
'A list of paths to include when searching for referenced objects,' " with the namespace separated by a ':'. Example: "
" with the namespace separated by a ':'. Example: " '/foo/bar:Foo::Bar::%(namespace)s'),
'/foo/bar:Foo::Bar::%(namespace)s'
),
) )
(opts, file_paths) = parser.parse_args() (opts, file_paths) = parser.parse_args()
@@ -242,28 +233,23 @@ if __name__ == '__main__':
sys.exit(0) # This is OK as a no-op sys.exit(0) # This is OK as a no-op
# Unless in bundle mode, only one file should be specified. # Unless in bundle mode, only one file should be specified.
if ( if (opts.generator not in ('cpp-bundle-registration', 'cpp-bundle-schema')
opts.generator not in ('cpp-bundle-registration', 'cpp-bundle-schema') and len(file_paths) > 1):
and len(file_paths) > 1
):
# TODO(sashab): Could also just use file_paths[0] here and not complain. # TODO(sashab): Could also just use file_paths[0] here and not complain.
raise Exception( raise Exception(
'Unless in bundle mode, only one file can be specified at a time.' 'Unless in bundle mode, only one file can be specified at a time.')
)
def split_path_and_namespace(path_and_namespace): def split_path_and_namespace(path_and_namespace):
if ':' not in path_and_namespace: if ':' not in path_and_namespace:
raise ValueError( raise ValueError(
'Invalid include rule "%s". Rules must be of the form path:namespace' 'Invalid include rule "%s". Rules must be of the form path:namespace'
% path_and_namespace % path_and_namespace)
)
return path_and_namespace.split(':', 1) return path_and_namespace.split(':', 1)
include_rules = [] include_rules = []
if opts.include_rules: if opts.include_rules:
include_rules = list( include_rules = list(
map(split_path_and_namespace, shlex.split(opts.include_rules)) map(split_path_and_namespace, shlex.split(opts.include_rules)))
)
result = GenerateSchema( result = GenerateSchema(
opts.generator, opts.generator,

@@ -26,6 +26,7 @@ def _RemoveKey(node, key, type_restriction):
for value in node: for value in node:
_RemoveKey(value, key, type_restriction) _RemoveKey(value, key, type_restriction)
def _RemoveUnneededFields(schema): def _RemoveUnneededFields(schema):
"""Returns a copy of |schema| with fields that aren't necessary at runtime """Returns a copy of |schema| with fields that aren't necessary at runtime
removed. removed.
@@ -41,6 +42,7 @@ def _RemoveUnneededFields(schema):
_RemoveKey(ret, 'manifest_keys', object) _RemoveKey(ret, 'manifest_keys', object)
return ret return ret
def _PrefixSchemaWithNamespace(schema): def _PrefixSchemaWithNamespace(schema):
"""Modifies |schema| in place to prefix all types and references with a """Modifies |schema| in place to prefix all types and references with a
namespace, if they aren't already qualified. That is, in the tabs API, this namespace, if they aren't already qualified. That is, in the tabs API, this
@@ -49,10 +51,11 @@ def _PrefixSchemaWithNamespace(schema):
""" """
assert isinstance(schema, dict), "Schema is unexpected type" assert isinstance(schema, dict), "Schema is unexpected type"
namespace = schema['namespace'] namespace = schema['namespace']
def prefix(obj, key, mandatory): def prefix(obj, key, mandatory):
if not key in obj: if not key in obj:
assert not mandatory, ( assert not mandatory, ('Required key "%s" is not present in object.' %
'Required key "%s" is not present in object.' % key) key)
return return
assert type(obj[key]) is str assert type(obj[key]) is str
if obj[key].find('.') == -1: if obj[key].find('.') == -1:
@@ -73,6 +76,7 @@ def _PrefixSchemaWithNamespace(schema):
prefix(val, '$ref', False) prefix(val, '$ref', False)
for key, sub_val in val.items(): for key, sub_val in val.items():
prefix_refs(sub_val) prefix_refs(sub_val)
prefix_refs(schema) prefix_refs(schema)
return schema return schema
@@ -81,15 +85,8 @@ class CppBundleGenerator(object):
"""This class contains methods to generate code based on multiple schemas. """This class contains methods to generate code based on multiple schemas.
""" """
def __init__(self, def __init__(self, root, model, api_defs, cpp_type_generator,
root, cpp_namespace_pattern, bundle_name, source_file_dir, impl_dir):
model,
api_defs,
cpp_type_generator,
cpp_namespace_pattern,
bundle_name,
source_file_dir,
impl_dir):
self._root = root self._root = root
self._model = model self._model = model
self._api_defs = api_defs self._api_defs = api_defs
@@ -196,9 +193,9 @@ class CppBundleGenerator(object):
if function.nocompile: if function.nocompile:
continue continue
namespace_types_name = JsFunctionNameToClassName( namespace_types_name = JsFunctionNameToClassName(
namespace.name, type_.name) namespace.name, type_.name)
c.Concat(self._GenerateRegistrationEntry(namespace_types_name, c.Concat(
function)) self._GenerateRegistrationEntry(namespace_types_name, function))
if namespace_ifdefs is not None: if namespace_ifdefs is not None:
c.Append("#endif // %s" % namespace_ifdefs, indent_level=0) c.Append("#endif // %s" % namespace_ifdefs, indent_level=0)
@@ -218,6 +215,7 @@ class CppBundleGenerator(object):
class _APIHGenerator(object): class _APIHGenerator(object):
"""Generates the header for API registration / declaration""" """Generates the header for API registration / declaration"""
def __init__(self, cpp_bundle): def __init__(self, cpp_bundle):
self._bundle = cpp_bundle self._bundle = cpp_bundle
@@ -234,7 +232,7 @@ class _APIHGenerator(object):
self._bundle._GenerateBundleClass('GeneratedFunctionRegistry')) self._bundle._GenerateBundleClass('GeneratedFunctionRegistry'))
c.Sblock(' public:') c.Sblock(' public:')
c.Append('static void RegisterAll(' c.Append('static void RegisterAll('
'ExtensionFunctionRegistry* registry);') 'ExtensionFunctionRegistry* registry);')
c.Eblock('};') c.Eblock('};')
c.Append() c.Append()
c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace)) c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace))
@@ -251,9 +249,8 @@ class _APICCGenerator(object):
c = code_util.Code() c = code_util.Code()
c.Append(cpp_util.CHROMIUM_LICENSE) c.Append(cpp_util.CHROMIUM_LICENSE)
c.Append() c.Append()
c.Append('#include "%s"' % ( c.Append('#include "%s"' % (cpp_util.ToPosixPath(
cpp_util.ToPosixPath(os.path.join(self._bundle._impl_dir, os.path.join(self._bundle._impl_dir, 'generated_api_registration.h'))))
'generated_api_registration.h'))))
c.Append() c.Append()
c.Append('#include "build/build_config.h"') c.Append('#include "build/build_config.h"')
c.Append('#include "build/chromeos_buildflags.h"') c.Append('#include "build/chromeos_buildflags.h"')
@@ -261,17 +258,15 @@ class _APICCGenerator(object):
for namespace in self._bundle._model.namespaces.values(): for namespace in self._bundle._model.namespaces.values():
namespace_name = namespace.unix_name.replace("experimental_", "") namespace_name = namespace.unix_name.replace("experimental_", "")
implementation_header = namespace.compiler_options.get( implementation_header = namespace.compiler_options.get(
"implemented_in", "implemented_in", "%s/%s/%s_api.h" %
"%s/%s/%s_api.h" % (self._bundle._impl_dir, (self._bundle._impl_dir, namespace_name, namespace_name))
namespace_name,
namespace_name))
if not os.path.exists( if not os.path.exists(
os.path.join(self._bundle._root, os.path.join(self._bundle._root,
os.path.normpath(implementation_header))): os.path.normpath(implementation_header))):
if "implemented_in" in namespace.compiler_options: if "implemented_in" in namespace.compiler_options:
raise ValueError('Header file for namespace "%s" specified in ' raise ValueError('Header file for namespace "%s" specified in '
'compiler_options not found: %s' % 'compiler_options not found: %s' %
(namespace.unix_name, implementation_header)) (namespace.unix_name, implementation_header))
continue continue
ifdefs = self._bundle._GetPlatformIfdefs(namespace) ifdefs = self._bundle._GetPlatformIfdefs(namespace)
if ifdefs is not None: if ifdefs is not None:
@@ -283,7 +278,7 @@ class _APICCGenerator(object):
c.Append("#endif // %s" % ifdefs, indent_level=0) c.Append("#endif // %s" % ifdefs, indent_level=0)
c.Append() c.Append()
c.Append('#include ' c.Append('#include '
'"extensions/browser/extension_function_registry.h"') '"extensions/browser/extension_function_registry.h"')
c.Append() c.Append()
c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace)) c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace))
c.Append() c.Append()
@@ -296,6 +291,7 @@ class _APICCGenerator(object):
class _SchemasHGenerator(object): class _SchemasHGenerator(object):
"""Generates a code_util.Code object for the generated schemas .h file""" """Generates a code_util.Code object for the generated schemas .h file"""
def __init__(self, cpp_bundle): def __init__(self, cpp_bundle):
self._bundle = cpp_bundle self._bundle = cpp_bundle
@@ -322,8 +318,7 @@ class _SchemasHGenerator(object):
def _FormatNameAsConstant(name): def _FormatNameAsConstant(name):
"""Formats a name to be a C++ constant of the form kConstantName""" """Formats a name to be a C++ constant of the form kConstantName"""
name = '%s%s' % (name[0].upper(), name[1:]) name = '%s%s' % (name[0].upper(), name[1:])
return 'k%s' % re.sub('_[a-z]', return 'k%s' % re.sub('_[a-z]', lambda m: m.group(0)[1].upper(),
lambda m: m.group(0)[1].upper(),
name.replace('.', '_')) name.replace('.', '_'))
@@ -337,9 +332,8 @@ class _SchemasCCGenerator(object):
c = code_util.Code() c = code_util.Code()
c.Append(cpp_util.CHROMIUM_LICENSE) c.Append(cpp_util.CHROMIUM_LICENSE)
c.Append() c.Append()
c.Append('#include "%s"' % ( c.Append('#include "%s"' % (cpp_util.ToPosixPath(
cpp_util.ToPosixPath(os.path.join(self._bundle._source_file_dir, os.path.join(self._bundle._source_file_dir, 'generated_schemas.h'))))
'generated_schemas.h'))))
c.Append() c.Append()
c.Append('#include <algorithm>') c.Append('#include <algorithm>')
c.Append('#include <iterator>') c.Append('#include <iterator>')
@@ -351,7 +345,7 @@ class _SchemasCCGenerator(object):
for api in self._bundle._api_defs: for api in self._bundle._api_defs:
namespace = self._bundle._model.namespaces[api.get('namespace')] namespace = self._bundle._model.namespaces[api.get('namespace')]
json_content = json.dumps(_PrefixSchemaWithNamespace( json_content = json.dumps(_PrefixSchemaWithNamespace(
_RemoveUnneededFields(api)), _RemoveUnneededFields(api)),
separators=(',', ':')) separators=(',', ':'))
# This will output a valid JSON C string. Note that some schemas are # This will output a valid JSON C string. Note that some schemas are
# too large to compile on windows. Split the JSON up into several # too large to compile on windows. Split the JSON up into several
@@ -381,8 +375,10 @@ class _SchemasCCGenerator(object):
c.Append('static constexpr auto kSchemas = ' c.Append('static constexpr auto kSchemas = '
'base::MakeFixedFlatMap<std::string_view, std::string_view>({') 'base::MakeFixedFlatMap<std::string_view, std::string_view>({')
c.Sblock() c.Sblock()
namespaces = [self._bundle._model.namespaces[api.get('namespace')].name namespaces = [
for api in self._bundle._api_defs] self._bundle._model.namespaces[api.get('namespace')].name
for api in self._bundle._api_defs
]
for namespace in sorted(namespaces): for namespace in sorted(namespaces):
schema_constant_name = _FormatNameAsConstant(namespace) schema_constant_name = _FormatNameAsConstant(namespace)
c.Append('{"%s", %s},' % (namespace, schema_constant_name)) c.Append('{"%s", %s},' % (namespace, schema_constant_name))

@@ -10,26 +10,29 @@ import json_schema
import os import os
import unittest import unittest
def _createCppBundleGenerator(file_path): def _createCppBundleGenerator(file_path):
json_object = json_schema.Load(file_path) json_object = json_schema.Load(file_path)
model = Model() model = Model()
model.AddNamespace(json_object[0], file_path) model.AddNamespace(json_object[0], file_path)
cpp_bundle_generator = CppBundleGenerator( cpp_bundle_generator = CppBundleGenerator(None, model, None, None,
None, model, None, None, 'generated_api_schemas', 'generated_api_schemas', None, None,
None, None, None) None)
return (cpp_bundle_generator, model) return (cpp_bundle_generator, model)
def _getPlatformIfdefs(cpp_bundle_generator, model): def _getPlatformIfdefs(cpp_bundle_generator, model):
return cpp_bundle_generator._GetPlatformIfdefs( return cpp_bundle_generator._GetPlatformIfdefs(
list(list(model.namespaces.values())[0].functions.values())[0]) list(list(model.namespaces.values())[0].functions.values())[0])
class CppBundleGeneratorTest(unittest.TestCase): class CppBundleGeneratorTest(unittest.TestCase):
def testIfDefsForWinLinux(self): def testIfDefsForWinLinux(self):
cpp_bundle_generator, model = _createCppBundleGenerator( cpp_bundle_generator, model = _createCppBundleGenerator(
'test/function_platform_win_linux.json') 'test/function_platform_win_linux.json')
self.assertEqual( self.assertEqual('BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)',
'BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)', _getPlatformIfdefs(cpp_bundle_generator, model))
_getPlatformIfdefs(cpp_bundle_generator, model))
def testIfDefsForAll(self): def testIfDefsForAll(self):
cpp_bundle_generator, model = _createCppBundleGenerator( cpp_bundle_generator, model = _createCppBundleGenerator(
@@ -43,7 +46,7 @@ class CppBundleGeneratorTest(unittest.TestCase):
cpp_bundle_generator, model = _createCppBundleGenerator( cpp_bundle_generator, model = _createCppBundleGenerator(
'test/function_platform_chromeos.json') 'test/function_platform_chromeos.json')
self.assertEqual('BUILDFLAG(IS_CHROMEOS_ASH)', self.assertEqual('BUILDFLAG(IS_CHROMEOS_ASH)',
_getPlatformIfdefs(cpp_bundle_generator, model)) _getPlatformIfdefs(cpp_bundle_generator, model))
if __name__ == '__main__': if __name__ == '__main__':

@@ -5,7 +5,9 @@
from cc_generator import CCGenerator from cc_generator import CCGenerator
from h_generator import HGenerator from h_generator import HGenerator
class CppGenerator(object): class CppGenerator(object):
def __init__(self, type_generator): def __init__(self, type_generator):
self.h_generator = HGenerator(type_generator) self.h_generator = HGenerator(type_generator)
self.cc_generator = CCGenerator(type_generator) self.cc_generator = CCGenerator(type_generator)

@@ -2,6 +2,8 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
class CppNamespaceEnvironment(object): class CppNamespaceEnvironment(object):
def __init__(self, namespace_pattern): def __init__(self, namespace_pattern):
self.namespace_pattern = namespace_pattern self.namespace_pattern = namespace_pattern

@@ -8,11 +8,13 @@ import cpp_util
from json_parse import OrderedDict from json_parse import OrderedDict
import schema_util import schema_util
class _TypeDependency(object): class _TypeDependency(object):
"""Contains information about a dependency a namespace has on a type: the """Contains information about a dependency a namespace has on a type: the
type's model, and whether that dependency is "hard" meaning that it cannot be type's model, and whether that dependency is "hard" meaning that it cannot be
forward declared. forward declared.
""" """
def __init__(self, type_, hard=False): def __init__(self, type_, hard=False):
self.type_ = type_ self.type_ = type_
self.hard = hard self.hard = hard
@@ -25,6 +27,7 @@ class CppTypeGenerator(object):
"""Manages the types of properties and provides utilities for getting the """Manages the types of properties and provides utilities for getting the
C++ type out of a model.Property C++ type out of a model.Property
""" """
def __init__(self, model, namespace_resolver, default_namespace=None): def __init__(self, model, namespace_resolver, default_namespace=None):
"""Creates a cpp_type_generator. The given root_namespace should be of the """Creates a cpp_type_generator. The given root_namespace should be of the
format extensions::api::sub. The generator will generate code suitable for format extensions::api::sub. The generator will generate code suitable for
@@ -40,9 +43,8 @@ class CppTypeGenerator(object):
typename in an optional, for the regular case, or uses a base::expected for typename in an optional, for the regular case, or uses a base::expected for
when it should support string errors. when it should support string errors.
""" """
return (('base::expected<{typename}, std::u16string>' return (('base::expected<{typename}, std::u16string>' if support_errors else
if support_errors else 'std::optional<{typename}>') 'std::optional<{typename}>').format(typename=typename))
.format(typename=typename))
def GetEnumNoneValue(self, type_, full_name=True): def GetEnumNoneValue(self, type_, full_name=True):
"""Gets the enum value in the given model. Property indicating no value has """Gets the enum value in the given model. Property indicating no value has
@@ -91,7 +93,7 @@ class CppTypeGenerator(object):
result = '' result = ''
for char in name: for char in name:
if char in {'_', '-'}: if char in {'_', '-'}:
change_to_upper=True change_to_upper = True
elif change_to_upper: elif change_to_upper:
# Numbers must be kept separate, for better readability (e.g. kX86_64). # Numbers must be kept separate, for better readability (e.g. kX86_64).
if char.isnumeric() and result and result[-1].isnumeric(): if char.isnumeric() and result and result[-1].isnumeric():
@@ -125,9 +127,9 @@ class CppTypeGenerator(object):
prefix = '{classname}::'.format(classname=classname) prefix = '{classname}::'.format(classname=classname)
# We kCamelCase the string, also removing any _ from the name, to allow # We kCamelCase the string, also removing any _ from the name, to allow
# SHOUTY_CASE keys to be kCamelCase as well. # SHOUTY_CASE keys to be kCamelCase as well.
return '{prefix}k{name}'.format( return '{prefix}k{name}'.format(prefix=prefix,
prefix=prefix, name=self.FormatStringForEnumValue(
name=self.FormatStringForEnumValue(enum_value.name)) enum_value.name))
def GetCppType(self, type_, is_optional=False): def GetCppType(self, type_, is_optional=False):
"""Translates a model.Property or model.Type into its C++ type. """Translates a model.Property or model.Type into its C++ type.
@@ -155,8 +157,7 @@ class CppTypeGenerator(object):
cpp_type = 'double' cpp_type = 'double'
elif type_.property_type == PropertyType.STRING: elif type_.property_type == PropertyType.STRING:
cpp_type = 'std::string' cpp_type = 'std::string'
elif type_.property_type in (PropertyType.ENUM, elif type_.property_type in (PropertyType.ENUM, PropertyType.OBJECT,
PropertyType.OBJECT,
PropertyType.CHOICES): PropertyType.CHOICES):
if self._default_namespace is type_.namespace: if self._default_namespace is type_.namespace:
cpp_type = cpp_util.Classname(type_.name) cpp_type = cpp_util.Classname(type_.name)
@@ -164,8 +165,7 @@ class CppTypeGenerator(object):
cpp_namespace = cpp_util.GetCppNamespace( cpp_namespace = cpp_util.GetCppNamespace(
type_.namespace.environment.namespace_pattern, type_.namespace.environment.namespace_pattern,
type_.namespace.unix_name) type_.namespace.unix_name)
cpp_type = '%s::%s' % (cpp_namespace, cpp_type = '%s::%s' % (cpp_namespace, cpp_util.Classname(type_.name))
cpp_util.Classname(type_.name))
elif type_.property_type == PropertyType.ANY: elif type_.property_type == PropertyType.ANY:
cpp_type = 'base::Value' cpp_type = 'base::Value'
elif type_.property_type == PropertyType.FUNCTION: elif type_.property_type == PropertyType.FUNCTION:
@@ -201,10 +201,9 @@ class CppTypeGenerator(object):
return cpp_type return cpp_type
def IsCopyable(self, type_): def IsCopyable(self, type_):
return not (self.FollowRef(type_).property_type in (PropertyType.ANY, return not (self.FollowRef(type_).property_type
PropertyType.ARRAY, in (PropertyType.ANY, PropertyType.ARRAY, PropertyType.OBJECT,
PropertyType.OBJECT, PropertyType.CHOICES))
PropertyType.CHOICES))
def GenerateForwardDeclarations(self): def GenerateForwardDeclarations(self):
"""Returns the forward declarations for self._default_namespace. """Returns the forward declarations for self._default_namespace.
@@ -212,17 +211,16 @@ class CppTypeGenerator(object):
c = Code() c = Code()
for namespace, deps in self._NamespaceTypeDependencies().items(): for namespace, deps in self._NamespaceTypeDependencies().items():
filtered_deps = [ filtered_deps = [
dep for dep in deps dep for dep in deps
# Add more ways to forward declare things as necessary. # Add more ways to forward declare things as necessary.
if (not dep.hard and if (not dep.hard and dep.type_.property_type in (PropertyType.CHOICES,
dep.type_.property_type in (PropertyType.CHOICES, PropertyType.OBJECT))
PropertyType.OBJECT))] ]
if not filtered_deps: if not filtered_deps:
continue continue
cpp_namespace = cpp_util.GetCppNamespace( cpp_namespace = cpp_util.GetCppNamespace(
namespace.environment.namespace_pattern, namespace.environment.namespace_pattern, namespace.unix_name)
namespace.unix_name)
c.Concat(cpp_util.OpenNamespace(cpp_namespace)) c.Concat(cpp_util.OpenNamespace(cpp_namespace))
for dep in filtered_deps: for dep in filtered_deps:
c.Append('struct %s;' % dep.type_.name) c.Append('struct %s;' % dep.type_.name)
@@ -236,9 +234,9 @@ class CppTypeGenerator(object):
# The inclusion of the std::string_view header is dependent on either the # The inclusion of the std::string_view header is dependent on either the
# presence of enums, or manifest keys. # presence of enums, or manifest keys.
include_string_view = (self._default_namespace.manifest_keys or include_string_view = (self._default_namespace.manifest_keys or any(
any(type_.property_type is PropertyType.ENUM for type_ in type_.property_type is PropertyType.ENUM
self._default_namespace.types.values())) for type_ in self._default_namespace.types.values()))
if include_string_view: if include_string_view:
c.Append('#include <string_view>') c.Append('#include <string_view>')
@@ -246,11 +244,10 @@ class CppTypeGenerator(object):
# The header for `base::expected` should be included whenever error messages # The header for `base::expected` should be included whenever error messages
# are supposed to be returned, which only occurs with object, choices, or # are supposed to be returned, which only occurs with object, choices, or
# functions. # functions.
if (generate_error_messages and ( if (generate_error_messages
len(self._default_namespace.functions.values()) or and (len(self._default_namespace.functions.values()) or any(
any(type_.property_type in type_.property_type in [PropertyType.OBJECT, PropertyType.CHOICES]
[PropertyType.OBJECT, PropertyType.CHOICES] for type_ in for type_ in self._default_namespace.types.values()))):
self._default_namespace.types.values()))):
c.Append('#include "base/types/expected.h"') c.Append('#include "base/types/expected.h"')
# Note: It's possible that there are multiple dependencies from the same # Note: It's possible that there are multiple dependencies from the same
@@ -361,11 +358,7 @@ class CppTypeGenerator(object):
cpp_value = '"%s"' % cpp_value cpp_value = '"%s"' % cpp_value
cpp_type = 'char' cpp_type = 'char'
cpp_name = '%s[]' % cpp_name cpp_name = '%s[]' % cpp_name
c.Append(line % { c.Append(line % {"type": cpp_type, "name": cpp_name, "value": cpp_value})
"type": cpp_type,
"name": cpp_name,
"value": cpp_value
})
else: else:
has_child_code = False has_child_code = False
c.Sblock('namespace %s {' % prop.name) c.Sblock('namespace %s {' % prop.name)

@@ -12,7 +12,9 @@ import unittest
from collections import defaultdict from collections import defaultdict
class _FakeSchemaLoader(object): class _FakeSchemaLoader(object):
def __init__(self, model): def __init__(self, model):
self._model = model self._model = model
@@ -22,13 +24,15 @@ class _FakeSchemaLoader(object):
return default if type_name in default.types else None return default if type_name in default.types else None
return self._model.namespaces[parts[0]] return self._model.namespaces[parts[0]]
class CppTypeGeneratorTest(unittest.TestCase): class CppTypeGeneratorTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.models = defaultdict(model.Model) self.models = defaultdict(model.Model)
forbidden_json = CachedLoad('test/forbidden.json') forbidden_json = CachedLoad('test/forbidden.json')
self.models['forbidden'].AddNamespace( self.models['forbidden'].AddNamespace(forbidden_json[0],
forbidden_json[0], 'path/to/forbidden.json') 'path/to/forbidden.json')
permissions_json = CachedLoad('test/permissions.json') permissions_json = CachedLoad('test/permissions.json')
self.permissions = self.models['permissions'].AddNamespace( self.permissions = self.models['permissions'].AddNamespace(
@@ -59,12 +63,13 @@ class CppTypeGeneratorTest(unittest.TestCase):
objects_movable_idl = idl_schema.Load('test/objects_movable.idl') objects_movable_idl = idl_schema.Load('test/objects_movable.idl')
self.objects_movable = self.models['objects_movable'].AddNamespace( self.objects_movable = self.models['objects_movable'].AddNamespace(
objects_movable_idl[0], 'path/to/objects_movable.idl', objects_movable_idl[0],
'path/to/objects_movable.idl',
include_compiler_options=True) include_compiler_options=True)
self.simple_api_json = CachedLoad('test/simple_api.json') self.simple_api_json = CachedLoad('test/simple_api.json')
self.models['simple_api'].AddNamespace( self.models['simple_api'].AddNamespace(self.simple_api_json[0],
self.simple_api_json[0], 'path/to/simple_api.json') 'path/to/simple_api.json')
self.crossref_enums_json = CachedLoad('test/crossref_enums.json') self.crossref_enums_json = CachedLoad('test/crossref_enums.json')
self.crossref_enums = self.models['crossref_enums'].AddNamespace( self.crossref_enums = self.models['crossref_enums'].AddNamespace(
@@ -73,8 +78,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
self.crossref_enums_array_json = CachedLoad( self.crossref_enums_array_json = CachedLoad(
'test/crossref_enums_array.json') 'test/crossref_enums_array.json')
self.models['crossref_enums_array'].AddNamespace( self.models['crossref_enums_array'].AddNamespace(
self.crossref_enums_array_json[0], self.crossref_enums_array_json[0], 'path/to/crossref_enums_array.json')
'path/to/crossref_enums_array.json')
def testGenerateIncludesAndForwardDeclarations(self): def testGenerateIncludesAndForwardDeclarations(self):
m = model.Model() m = model.Model()
@@ -88,7 +92,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
self.assertEqual('', manager.GenerateIncludes().Render()) self.assertEqual('', manager.GenerateIncludes().Render())
self.assertEqual('#include "path/to/tabs.h"', self.assertEqual('#include "path/to/tabs.h"',
manager.GenerateIncludes(include_soft=True).Render()) manager.GenerateIncludes(include_soft=True).Render())
self.assertEqual( self.assertEqual(
'namespace tabs {\n' 'namespace tabs {\n'
'struct Tab;\n' 'struct Tab;\n'
@@ -96,14 +100,14 @@ class CppTypeGeneratorTest(unittest.TestCase):
manager.GenerateForwardDeclarations().Render()) manager.GenerateForwardDeclarations().Render())
m = model.Model() m = model.Model()
m.AddNamespace(self.windows_json[0], m.AddNamespace(
'path/to/windows.json', self.windows_json[0],
environment=CppNamespaceEnvironment( 'path/to/windows.json',
'foo::bar::%(namespace)s')) environment=CppNamespaceEnvironment('foo::bar::%(namespace)s'))
m.AddNamespace(self.tabs_json[0], m.AddNamespace(
'path/to/tabs.json', self.tabs_json[0],
environment=CppNamespaceEnvironment( 'path/to/tabs.json',
'foo::bar::%(namespace)s')) environment=CppNamespaceEnvironment('foo::bar::%(namespace)s'))
manager = CppTypeGenerator(m, _FakeSchemaLoader(m)) manager = CppTypeGenerator(m, _FakeSchemaLoader(m))
self.assertEqual( self.assertEqual(
'namespace foo {\n' 'namespace foo {\n'
@@ -134,9 +138,10 @@ class CppTypeGeneratorTest(unittest.TestCase):
manager = CppTypeGenerator(m, manager = CppTypeGenerator(m,
_FakeSchemaLoader(m), _FakeSchemaLoader(m),
default_namespace=dependency_tester) default_namespace=dependency_tester)
self.assertEqual('#include "path/to/browser_action.h"\n' self.assertEqual(
'#include "path/to/font_settings.h"', '#include "path/to/browser_action.h"\n'
manager.GenerateIncludes().Render()) '#include "path/to/font_settings.h"',
manager.GenerateIncludes().Render())
self.assertEqual('', manager.GenerateForwardDeclarations().Render()) self.assertEqual('', manager.GenerateForwardDeclarations().Render())
def testGetCppTypeSimple(self): def testGetCppTypeSimple(self):
@@ -167,7 +172,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
def testGetCppTypeArray(self): def testGetCppTypeArray(self):
manager = CppTypeGenerator(self.models.get('windows'), manager = CppTypeGenerator(self.models.get('windows'),
_FakeSchemaLoader(None)) _FakeSchemaLoader(None))
self.assertEqual( self.assertEqual(
'std::vector<Window>', 'std::vector<Window>',
manager.GetCppType( manager.GetCppType(
@@ -183,9 +188,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
_FakeSchemaLoader(None)) _FakeSchemaLoader(None))
self.assertEqual( self.assertEqual(
'std::vector<MovablePod>', 'std::vector<MovablePod>',
manager.GetCppType( manager.GetCppType(self.objects_movable.types['MovableParent'].
self.objects_movable.types['MovableParent']. properties['pods'].type_))
properties['pods'].type_))
def testGetCppTypeLocalRef(self): def testGetCppTypeLocalRef(self):
manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None)) manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None))
@@ -211,7 +215,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
def testGetCppTypeWithPadForGeneric(self): def testGetCppTypeWithPadForGeneric(self):
manager = CppTypeGenerator(self.models.get('permissions'), manager = CppTypeGenerator(self.models.get('permissions'),
_FakeSchemaLoader(None)) _FakeSchemaLoader(None))
self.assertEqual('std::vector<std::string>', self.assertEqual(
'std::vector<std::string>',
manager.GetCppType( manager.GetCppType(
self.permissions.types['Permissions'].properties['origins'].type_)) self.permissions.types['Permissions'].properties['origins'].type_))
self.assertEqual( self.assertEqual(
@@ -235,7 +240,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
_FakeSchemaLoader(m)) _FakeSchemaLoader(m))
self.assertEqual('#include "path/to/simple_api.h"', self.assertEqual('#include "path/to/simple_api.h"',
manager.GenerateIncludes().Render()) manager.GenerateIncludes().Render())
def testHardIncludesForEnumArrays(self): def testHardIncludesForEnumArrays(self):
"""Tests that enums in arrays generate hard includes. Note that it's """Tests that enums in arrays generate hard includes. Note that it's
@@ -253,7 +258,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
_FakeSchemaLoader(m)) _FakeSchemaLoader(m))
self.assertEqual('#include "path/to/simple_api.h"', self.assertEqual('#include "path/to/simple_api.h"',
manager.GenerateIncludes().Render()) manager.GenerateIncludes().Render())
def testCrossNamespaceGetEnumDefaultValue(self): def testCrossNamespaceGetEnumDefaultValue(self):
m = model.Model() m = model.Model()
@@ -272,9 +277,9 @@ class CppTypeGeneratorTest(unittest.TestCase):
self.assertEqual( self.assertEqual(
'namespace1::api::simple_api::TestEnum()', 'namespace1::api::simple_api::TestEnum()',
manager.GetEnumDefaultValue( manager.GetEnumDefaultValue(
self.crossref_enums.types['CrossrefType'] self.crossref_enums.types['CrossrefType'].
.properties['testEnumOptional'].type_, properties['testEnumOptional'].type_, self.crossref_enums))
self.crossref_enums))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@@ -1,7 +1,6 @@
# Copyright 2012 The Chromium Authors # Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Utilies and constants specific to Chromium C++ code. """Utilies and constants specific to Chromium C++ code.
""" """
@@ -12,11 +11,9 @@ import os
import posixpath import posixpath
import re import re
CHROMIUM_LICENSE = ( CHROMIUM_LICENSE = ("""// Copyright %d The Chromium Authors
"""// Copyright %d The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.""" % datetime.now().year // found in the LICENSE file.""" % datetime.now().year)
)
GENERATED_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITION IN GENERATED_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITION IN
// %s // %s
// by tools/json_schema_compiler. // by tools/json_schema_compiler.
@@ -33,6 +30,7 @@ GENERATED_FEATURE_MESSAGE = """// GENERATED FROM THE FEATURE DEFINITIONS IN
// DO NOT EDIT. // DO NOT EDIT.
""" """
def Classname(s): def Classname(s):
"""Translates a namespace name or function name into something more """Translates a namespace name or function name into something more
suited to C++. suited to C++.
@@ -56,6 +54,7 @@ def Classname(s):
result = '_' + result result = '_' + result
return result return result
def GetAsFundamentalValue(type_, src): def GetAsFundamentalValue(type_, src):
"""Returns the C++ code for retrieving a fundamental type from a """Returns the C++ code for retrieving a fundamental type from a
Value into a variable. Value into a variable.
@@ -68,9 +67,9 @@ def GetAsFundamentalValue(type_, src):
s = '%s.GetIfDouble()' s = '%s.GetIfDouble()'
elif type_.property_type == PropertyType.INTEGER: elif type_.property_type == PropertyType.INTEGER:
s = '%s.GetIfInt()' s = '%s.GetIfInt()'
elif (type_.property_type == PropertyType.STRING or elif (type_.property_type == PropertyType.STRING
(type_.property_type == PropertyType.FUNCTION and or (type_.property_type == PropertyType.FUNCTION
type_.is_serializable_function)): and type_.is_serializable_function)):
s = '%s.GetIfString()' s = '%s.GetIfString()'
else: else:
raise ValueError('Type %s is not a fundamental value' % type_.name) raise ValueError('Type %s is not a fundamental value' % type_.name)
@@ -104,43 +103,49 @@ def GetValueType(type_):
raise ValueError('Invalid type: %s' % type_.name) raise ValueError('Invalid type: %s' % type_.name)
def ShouldUseStdOptional(type_): def ShouldUseStdOptional(type_):
"""Called to validate whether or not an optional value should be represented """Called to validate whether or not an optional value should be represented
with std::optional. This function is a temporary utility, while optional with std::optional. This function is a temporary utility, while optional
fields are gradually migrated away from using std::unique_ptr. fields are gradually migrated away from using std::unique_ptr.
""" """
if type_.property_type in (PropertyType.ANY, if type_.property_type in (
PropertyType.ARRAY, PropertyType.ANY,
PropertyType.BINARY, PropertyType.ARRAY,
PropertyType.BOOLEAN, PropertyType.BINARY,
PropertyType.CHOICES, PropertyType.BOOLEAN,
PropertyType.DOUBLE, PropertyType.CHOICES,
PropertyType.FUNCTION, PropertyType.DOUBLE,
PropertyType.INTEGER, PropertyType.FUNCTION,
PropertyType.OBJECT, PropertyType.INTEGER,
PropertyType.STRING): PropertyType.OBJECT,
PropertyType.STRING,
):
return True return True
return False return False
def GetParameterDeclaration(param, type_): def GetParameterDeclaration(param, type_):
"""Gets a parameter declaration of a given model.Property and its C++ """Gets a parameter declaration of a given model.Property and its C++
type. type.
""" """
if param.type_.property_type in (PropertyType.ANY, if param.type_.property_type in (
PropertyType.ARRAY, PropertyType.ANY,
PropertyType.BINARY, PropertyType.ARRAY,
PropertyType.CHOICES, PropertyType.BINARY,
PropertyType.OBJECT, PropertyType.CHOICES,
PropertyType.REF, PropertyType.OBJECT,
PropertyType.STRING): PropertyType.REF,
PropertyType.STRING,
):
arg = 'const %(type)s& %(name)s' arg = 'const %(type)s& %(name)s'
else: else:
arg = '%(type)s %(name)s' arg = '%(type)s %(name)s'
return arg % { return arg % {
'type': type_, 'type': type_,
'name': param.unix_name, 'name': param.unix_name,
} }
@@ -150,10 +155,10 @@ def GenerateIfndefName(file_path):
e.g chrome/extensions/gen/file.h becomes CHROME_EXTENSIONS_GEN_FILE_H__. e.g chrome/extensions/gen/file.h becomes CHROME_EXTENSIONS_GEN_FILE_H__.
""" """
return (('%s__' % file_path).upper() return (('%s__' % file_path).upper() \
.replace('\\', '_') .replace('\\', '_') \
.replace('/', '_') .replace('/', '_') \
.replace('-', '_') .replace('-', '_') \
.replace('.', '_')) .replace('.', '_'))
@@ -180,7 +185,7 @@ def FeatureNameToConstantName(feature_name):
"""Returns a kName for a feature's name. """Returns a kName for a feature's name.
""" """
return ('k' + ''.join(word[0].upper() + word[1:] return ('k' + ''.join(word[0].upper() + word[1:]
for word in feature_name.replace('.', ' ').split())) for word in feature_name.replace('.', ' ').split()))
def UnixNameToConstantName(unix_name): def UnixNameToConstantName(unix_name):
@@ -189,6 +194,7 @@ def UnixNameToConstantName(unix_name):
""" """
return ('k' + ''.join(word.capitalize() for word in unix_name.split('_'))) return ('k' + ''.join(word.capitalize() for word in unix_name.split('_')))
def IsUnixName(s): def IsUnixName(s):
# type (str) -> bool # type (str) -> bool
"""Returns true if |s| is of the type unix_name i.e. only has lower cased """Returns true if |s| is of the type unix_name i.e. only has lower cased
@@ -196,6 +202,7 @@ def IsUnixName(s):
""" """
return all(x.islower() or x == '_' for x in s) and '_' in s return all(x.islower() or x == '_' for x in s) and '_' in s
def ToPosixPath(path): def ToPosixPath(path):
"""Returns |path| with separator converted to POSIX style. """Returns |path| with separator converted to POSIX style.
@@ -220,7 +227,7 @@ def GetCppNamespace(pattern, namespace):
# For some reason Windows builds escape the % characters, so unescape them. # For some reason Windows builds escape the % characters, so unescape them.
# This means that %% can never appear legitimately within a pattern, but # This means that %% can never appear legitimately within a pattern, but
# that's ok. It should never happen. # that's ok. It should never happen.
cpp_namespace = pattern.replace('%%', '%') % { 'namespace': namespace } cpp_namespace = pattern.replace('%%', '%') % {'namespace': namespace}
assert '%' not in cpp_namespace, \ assert '%' not in cpp_namespace, \
('Did not manage to fully substitute namespace "%s" into pattern "%s"' ('Did not manage to fully substitute namespace "%s" into pattern "%s"'
% (namespace, pattern)) % (namespace, pattern))

@@ -5,35 +5,27 @@
import unittest import unittest
from cpp_util import ( from cpp_util import (Classname, CloseNamespace, GetCppNamespace,
Classname, GenerateIfndefName, OpenNamespace)
CloseNamespace,
GetCppNamespace,
GenerateIfndefName,
OpenNamespace
)
class CppUtilTest(unittest.TestCase): class CppUtilTest(unittest.TestCase):
def testClassname(self): def testClassname(self):
self.assertEqual('Permissions', Classname('permissions')) self.assertEqual('Permissions', Classname('permissions'))
self.assertEqual('UpdateAllTheThings', self.assertEqual('UpdateAllTheThings', Classname('updateAllTheThings'))
Classname('updateAllTheThings'))
self.assertEqual('Aa_Bb_Cc', Classname('aa.bb.cc')) self.assertEqual('Aa_Bb_Cc', Classname('aa.bb.cc'))
def testNamespaceDeclaration(self): def testNamespaceDeclaration(self):
self.assertEqual('namespace foo {', self.assertEqual('namespace foo {', OpenNamespace('foo').Render())
OpenNamespace('foo').Render()) self.assertEqual('} // namespace foo', CloseNamespace('foo').Render())
self.assertEqual('} // namespace foo',
CloseNamespace('foo').Render())
self.assertEqual( self.assertEqual('namespace extensions {\n'
'namespace extensions {\n' 'namespace foo {',
'namespace foo {', OpenNamespace('extensions::foo').Render())
OpenNamespace('extensions::foo').Render()) self.assertEqual('} // namespace foo\n'
self.assertEqual( '} // namespace extensions',
'} // namespace foo\n' CloseNamespace('extensions::foo').Render())
'} // namespace extensions',
CloseNamespace('extensions::foo').Render())
self.assertEqual( self.assertEqual(
'namespace extensions {\n' 'namespace extensions {\n'

@@ -72,6 +72,7 @@ CC_FILE_END = """
} // namespace extensions } // namespace extensions
""" """
def ToPosixPath(path): def ToPosixPath(path):
"""Returns |path| with separator converted to POSIX style. """Returns |path| with separator converted to POSIX style.
@@ -79,11 +80,13 @@ def ToPosixPath(path):
""" """
return path.replace(os.path.sep, posixpath.sep) return path.replace(os.path.sep, posixpath.sep)
# Returns true if the list 'l' only contains strings that are a hex-encoded SHA1 # Returns true if the list 'l' only contains strings that are a hex-encoded SHA1
# hashes. # hashes.
def ListContainsOnlySha1Hashes(l): def ListContainsOnlySha1Hashes(l):
return len(list(filter(lambda s: not re.match("^[A-F0-9]{40}$", s), l))) == 0 return len(list(filter(lambda s: not re.match("^[A-F0-9]{40}$", s), l))) == 0
# A "grammar" for what is and isn't allowed in the features.json files. This # A "grammar" for what is and isn't allowed in the features.json files. This
# grammar has to list all possible keys and the requirements for each. The # grammar has to list all possible keys and the requirements for each. The
# format of each entry is: # format of each entry is:
@@ -170,19 +173,25 @@ FEATURE_GRAMMAR = ({
list: { list: {
'enum_map': { 'enum_map': {
'privileged_extension': 'privileged_extension':
'mojom::ContextType::kPrivilegedExtension', 'mojom::ContextType::kPrivilegedExtension',
'privileged_web_page': 'mojom::ContextType::kPrivilegedWebPage', 'privileged_web_page':
'content_script': 'mojom::ContextType::kContentScript', 'mojom::ContextType::kPrivilegedWebPage',
'content_script':
'mojom::ContextType::kContentScript',
'lock_screen_extension': 'lock_screen_extension':
'mojom::ContextType::kLockscreenExtension', 'mojom::ContextType::kLockscreenExtension',
'offscreen_extension': 'offscreen_extension':
'mojom::ContextType::kOffscreenExtension', 'mojom::ContextType::kOffscreenExtension',
'user_script': 'mojom::ContextType::kUserScript', 'user_script':
'web_page': 'mojom::ContextType::kWebPage', 'mojom::ContextType::kUserScript',
'webui': 'mojom::ContextType::kWebUi', 'web_page':
'webui_untrusted': 'mojom::ContextType::kUntrustedWebUi', 'mojom::ContextType::kWebPage',
'webui':
'mojom::ContextType::kWebUi',
'webui_untrusted':
'mojom::ContextType::kUntrustedWebUi',
'unprivileged_extension': 'unprivileged_extension':
'mojom::ContextType::kUnprivilegedExtension', 'mojom::ContextType::kUnprivilegedExtension',
}, },
'allow_all': True, 'allow_all': True,
'allow_empty': True 'allow_empty': True
@@ -210,12 +219,18 @@ FEATURE_GRAMMAR = ({
'extension_types': { 'extension_types': {
list: { list: {
'enum_map': { 'enum_map': {
'extension': 'Manifest::TYPE_EXTENSION', 'extension':
'hosted_app': 'Manifest::TYPE_HOSTED_APP', 'Manifest::TYPE_EXTENSION',
'legacy_packaged_app': 'Manifest::TYPE_LEGACY_PACKAGED_APP', 'hosted_app':
'platform_app': 'Manifest::TYPE_PLATFORM_APP', 'Manifest::TYPE_HOSTED_APP',
'shared_module': 'Manifest::TYPE_SHARED_MODULE', 'legacy_packaged_app':
'theme': 'Manifest::TYPE_THEME', 'Manifest::TYPE_LEGACY_PACKAGED_APP',
'platform_app':
'Manifest::TYPE_PLATFORM_APP',
'shared_module':
'Manifest::TYPE_SHARED_MODULE',
'theme':
'Manifest::TYPE_THEME',
'login_screen_extension': 'login_screen_extension':
'Manifest::TYPE_LOGIN_SCREEN_EXTENSION', 'Manifest::TYPE_LOGIN_SCREEN_EXTENSION',
'chromeos_system_extension': 'chromeos_system_extension':
@@ -288,10 +303,12 @@ FEATURE_GRAMMAR = ({
'session_types': { 'session_types': {
list: { list: {
'enum_map': { 'enum_map': {
'regular': 'mojom::FeatureSessionType::kRegular', 'regular':
'kiosk': 'mojom::FeatureSessionType::kKiosk', 'mojom::FeatureSessionType::kRegular',
'kiosk':
'mojom::FeatureSessionType::kKiosk',
'kiosk.autolaunched': 'kiosk.autolaunched':
'mojom::FeatureSessionType::kAutolaunchedKiosk', 'mojom::FeatureSessionType::kAutolaunchedKiosk',
} }
} }
}, },
@@ -301,21 +318,27 @@ FEATURE_GRAMMAR = ({
}, },
}) })
FEATURE_TYPES = ['APIFeature', 'BehaviorFeature', FEATURE_TYPES = [
'ManifestFeature', 'PermissionFeature'] 'APIFeature', 'BehaviorFeature', 'ManifestFeature', 'PermissionFeature'
]
def HasProperty(property_name, value): def HasProperty(property_name, value):
return property_name in value return property_name in value
def HasAtLeastOneProperty(property_names, value): def HasAtLeastOneProperty(property_names, value):
return any([HasProperty(name, value) for name in property_names]) return any([HasProperty(name, value) for name in property_names])
def DoesNotHaveAllProperties(property_names, value): def DoesNotHaveAllProperties(property_names, value):
return not all([HasProperty(name, value) for name in property_names]) return not all([HasProperty(name, value) for name in property_names])
def DoesNotHaveProperty(property_name, value): def DoesNotHaveProperty(property_name, value):
return property_name not in value return property_name not in value
def DoesNotHavePropertyInComplexFeature(property_name, feature, all_features): def DoesNotHavePropertyInComplexFeature(property_name, feature, all_features):
if type(feature) is ComplexFeature: if type(feature) is ComplexFeature:
for child_feature in feature.feature_list: for child_feature in feature.feature_list:
@@ -323,6 +346,7 @@ def DoesNotHavePropertyInComplexFeature(property_name, feature, all_features):
return False return False
return True return True
def IsEmptyContextsAllowed(feature, all_features): def IsEmptyContextsAllowed(feature, all_features):
# An alias feature wouldn't have the 'contexts' feature value. # An alias feature wouldn't have the 'contexts' feature value.
if feature.GetValue('source'): if feature.GetValue('source'):
@@ -338,12 +362,13 @@ def IsEmptyContextsAllowed(feature, all_features):
assert contexts, 'contexts must have been specified for the APIFeature' assert contexts, 'contexts must have been specified for the APIFeature'
allowlisted_empty_context_namespaces = [ allowlisted_empty_context_namespaces = [
'manifestTypes', 'manifestTypes',
'extensionsManifestTypes', 'extensionsManifestTypes',
'empty_contexts' # Only added for testing. 'empty_contexts' # Only added for testing.
] ]
return (contexts != '{}' or return (contexts != '{}'
feature.name in allowlisted_empty_context_namespaces) or feature.name in allowlisted_empty_context_namespaces)
def IsFeatureCrossReference(property_name, reverse_property_name, feature, def IsFeatureCrossReference(property_name, reverse_property_name, feature,
all_features): all_features):
@@ -374,6 +399,7 @@ def IsFeatureCrossReference(property_name, reverse_property_name, feature,
return True return True
return reverse_reference_value == ('"%s"' % feature.name) return reverse_reference_value == ('"%s"' % feature.name)
# Verifies that a feature with an allowlist is not available to hosted apps, # Verifies that a feature with an allowlist is not available to hosted apps,
# returning true on success. # returning true on success.
def DoesNotHaveAllowlistForHostedApps(value): def DoesNotHaveAllowlistForHostedApps(value):
@@ -428,92 +454,90 @@ def DoesNotHaveAllowlistForHostedApps(value):
SIMPLE_FEATURE_CPP_CLASSES = ({ SIMPLE_FEATURE_CPP_CLASSES = ({
'APIFeature': 'SimpleFeature', 'APIFeature': 'SimpleFeature',
'ManifestFeature': 'ManifestFeature', 'ManifestFeature': 'ManifestFeature',
'PermissionFeature': 'PermissionFeature', 'PermissionFeature': 'PermissionFeature',
'BehaviorFeature': 'SimpleFeature', 'BehaviorFeature': 'SimpleFeature',
}) })
VALIDATION = ({ VALIDATION = ({
'all': [ 'all': [
(partial(HasAtLeastOneProperty, ['channel', 'dependencies']), (partial(HasAtLeastOneProperty, ['channel', 'dependencies']),
'Features must specify either a channel or dependencies'), 'Features must specify either a channel or dependencies'),
(DoesNotHaveAllowlistForHostedApps, (DoesNotHaveAllowlistForHostedApps,
'Hosted apps are not allowed to use restricted features'), 'Hosted apps are not allowed to use restricted features'),
], ],
'APIFeature': [ 'APIFeature':
(partial(HasProperty, 'contexts'), [(partial(HasProperty,
'APIFeatures must specify the contexts property'), 'contexts'), 'APIFeatures must specify the contexts property'),
(partial(DoesNotHaveAllProperties, ['alias', 'source']), (partial(DoesNotHaveAllProperties, ['alias', 'source']),
'Features cannot specify both alias and source.') 'Features cannot specify both alias and source.')],
], 'ManifestFeature': [
'ManifestFeature': [ (partial(HasProperty, 'extension_types'),
(partial(HasProperty, 'extension_types'), 'ManifestFeatures must specify at least one extension type'),
'ManifestFeatures must specify at least one extension type'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'contexts'), 'contexts'), 'ManifestFeatures do not support contexts.'),
'ManifestFeatures do not support contexts.'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'alias'), 'alias'), 'ManifestFeatures do not support alias.'),
'ManifestFeatures do not support alias.'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'source'), 'source'), 'ManifestFeatures do not support source.'),
'ManifestFeatures do not support source.'), # The `required_buildflags` field is intended to be used to toggle the
# The `required_buildflags` field is intended to be used to toggle the # availability of certain APIs; if we support this for feature types
# availability of certain APIs; if we support this for feature types other # other than APIFeature, we may emit warnings that are visible to
# than APIFeature, we may emit warnings that are visible to developers which # developers which is not desirable.
# is not desirable. (partial(DoesNotHaveProperty, 'required_buildflags'),
(partial(DoesNotHaveProperty, 'required_buildflags'), 'ManifestFeatures do not support required_buildflags.'),
'ManifestFeatures do not support required_buildflags.'), ],
], 'BehaviorFeature': [
'BehaviorFeature': [ (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'alias'), 'alias'), 'BehaviorFeatures do not support alias.'),
'BehaviorFeatures do not support alias.'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'source'), 'source'), 'BehaviorFeatures do not support source.'),
'BehaviorFeatures do not support source.'), # The `required_buildflags` field is intended to be used to toggle the
(partial(DoesNotHaveProperty, 'required_buildflags'), # availability of certain APIs; if we support this for feature types
# The `required_buildflags` field is intended to be used to toggle the # other than APIFeature, we may emit warnings that are visible to
# availability of certain APIs; if we support this for feature types other # developers which is not desirable.
# than APIFeature, we may emit warnings that are visible to developers which (partial(DoesNotHaveProperty, 'required_buildflags'),
# is not desirable. 'BehaviorFeatures do not support required_buildflags.'),
'BehaviorFeatures do not support required_buildflags.'), ],
], 'PermissionFeature': [
'PermissionFeature': [ (partial(HasProperty, 'extension_types'),
(partial(HasProperty, 'extension_types'), 'PermissionFeatures must specify at least one extension type'),
'PermissionFeatures must specify at least one extension type'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'contexts'), 'contexts'), 'PermissionFeatures do not support contexts.'),
'PermissionFeatures do not support contexts.'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'alias'), 'alias'), 'PermissionFeatures do not support alias.'),
'PermissionFeatures do not support alias.'), (partial(DoesNotHaveProperty,
(partial(DoesNotHaveProperty, 'source'), 'source'), 'PermissionFeatures do not support source.'),
'PermissionFeatures do not support source.'), # The `required_buildflags` field is intended to be used to toggle the
(partial(DoesNotHaveProperty, 'required_buildflags'), # availability of certain APIs; if we support this for feature types
# The `required_buildflags` field is intended to be used to toggle the # other than APIFeature, we may emit warnings that are visible to
# availability of certain APIs; if we support this for feature types other # developers which is not desirable.
# than APIFeature, we may emit warnings that are visible to developers which (partial(DoesNotHaveProperty, 'required_buildflags'),
# is not desirable. 'PermissionFeatures do not support required_buildflags.'),
'PermissionFeatures do not support required_buildflags.'), ],
],
}) })
FINAL_VALIDATION = ({ FINAL_VALIDATION = ({
'all': [ 'all': [
# A complex feature requires at least one child entry at all times; with # A complex feature requires at least one child entry at all times; with
# `required_buildflags` it becomes harder to guarantee that this holds for # `required_buildflags` it becomes harder to guarantee that this holds
# every potential combination of the provided flags. # for every potential combination of the provided flags.
(partial(DoesNotHavePropertyInComplexFeature, 'required_buildflags'), (partial(DoesNotHavePropertyInComplexFeature, 'required_buildflags'),
'required_buildflags cannot be nested in a ComplexFeature'), 'required_buildflags cannot be nested in a ComplexFeature'),
], ],
'APIFeature': [ 'APIFeature':
(partial(IsFeatureCrossReference, 'alias', 'source'), [(partial(IsFeatureCrossReference, 'alias', 'source'),
'A feature alias property should reference a feature whose source ' 'A feature alias property should reference a feature whose source '
'property references it back.'), 'property references it back.'),
(partial(IsFeatureCrossReference, 'source', 'alias'), (partial(IsFeatureCrossReference, 'source', 'alias'),
'A feature source property should reference a feature whose alias ' 'A feature source property should reference a feature whose alias '
'property references it back.'), 'property references it back.'),
(IsEmptyContextsAllowed, (IsEmptyContextsAllowed,
'An empty contexts list is not allowed for this feature.') 'An empty contexts list is not allowed for this feature.')],
], 'ManifestFeature': [],
'ManifestFeature': [], 'BehaviorFeature': [],
'BehaviorFeature': [], 'PermissionFeature': []
'PermissionFeature': []
}) })
# These keys can not be set on a feature and are hence ignored. # These keys can not be set on a feature and are hence ignored.
@@ -523,6 +547,7 @@ IGNORED_KEYS = ['default_parent', 'required_buildflags']
# can be disabled for testing. # can be disabled for testing.
ENABLE_ASSERTIONS = True ENABLE_ASSERTIONS = True
def GetCodeForFeatureValues(feature_values): def GetCodeForFeatureValues(feature_values):
""" Gets the Code object for setting feature values for this object. """ """ Gets the Code object for setting feature values for this object. """
c = Code() c = Code()
@@ -533,10 +558,12 @@ def GetCodeForFeatureValues(feature_values):
c.Append('feature->set_%s(%s);' % (key, feature_values[key])) c.Append('feature->set_%s(%s);' % (key, feature_values[key]))
return c return c
class Feature(object): class Feature(object):
"""A representation of a single simple feature that can handle all parsing, """A representation of a single simple feature that can handle all parsing,
validation, and code generation. validation, and code generation.
""" """
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
self.has_parent = False self.has_parent = False
@@ -557,10 +584,10 @@ class Feature(object):
"""Adds an error relating to a particular key in the feature. """Adds an error relating to a particular key in the feature.
""" """
self.AddError('Error parsing feature "%s" at key "%s": %s' % self.AddError('Error parsing feature "%s" at key "%s": %s' %
(self.name, key, error)) (self.name, key, error))
def _GetCheckedValue(self, key, expected_type, expected_values, def _GetCheckedValue(self, key, expected_type, expected_values, enum_map,
enum_map, value): value):
"""Returns a string to be used in the generated C++ code for a given key's """Returns a string to be used in the generated C++ code for a given key's
python value, or None if the value is invalid. For example, if the python python value, or None if the value is invalid. For example, if the python
value is True, this returns 'true', for a string foo, this returns "foo", value is True, this returns 'true', for a string foo, this returns "foo",
@@ -737,13 +764,15 @@ class Feature(object):
return values return values
def GetErrors(self): def GetErrors(self):
return self.errors; return self.errors
class ComplexFeature(Feature): class ComplexFeature(Feature):
""" Complex feature - feature that is comprised of list of features. """ Complex feature - feature that is comprised of list of features.
Overall complex feature is available if any of contained Overall complex feature is available if any of contained
feature is available. feature is available.
""" """
def __init__(self, name): def __init__(self, name):
Feature.__init__(self, name) Feature.__init__(self, name)
self.feature_list = [] self.feature_list = []
@@ -779,11 +808,13 @@ class ComplexFeature(Feature):
errors.extend(feature.GetErrors()) errors.extend(feature.GetErrors())
return errors return errors
class FeatureCompiler(object): class FeatureCompiler(object):
"""A compiler to load, parse, and generate C++ code for a number of """A compiler to load, parse, and generate C++ code for a number of
features.json files.""" features.json files."""
def __init__(self, chrome_root, source_files, feature_type,
method_name, out_root, gen_dir_relpath, out_base_filename): def __init__(self, chrome_root, source_files, feature_type, method_name,
out_root, gen_dir_relpath, out_base_filename):
# See __main__'s ArgumentParser for documentation on these properties. # See __main__'s ArgumentParser for documentation on these properties.
self._chrome_root = chrome_root self._chrome_root = chrome_root
self._source_files = source_files self._source_files = source_files
@@ -808,7 +839,7 @@ class FeatureCompiler(object):
f_json = json_parse.Parse(f.read()) f_json = json_parse.Parse(f.read())
except: except:
print('FAILED: Exception encountered while loading "%s"' % print('FAILED: Exception encountered while loading "%s"' %
abs_source_file) abs_source_file)
raise raise
dupes = set(f_json) & set(self._json) dupes = set(f_json) & set(self._json)
assert not dupes, 'Duplicate keys found: %s' % list(dupes) assert not dupes, 'Duplicate keys found: %s' % list(dupes)
@@ -822,8 +853,8 @@ class FeatureCompiler(object):
no_parent_values = ['noparent' in v for v in feature_value] no_parent_values = ['noparent' in v for v in feature_value]
no_parent = all(no_parent_values) no_parent = all(no_parent_values)
assert no_parent or not any(no_parent_values), ( assert no_parent or not any(no_parent_values), (
'"%s:" All child features must contain the same noparent value' % '"%s:" All child features must contain the same noparent value' %
feature_name) feature_name)
else: else:
no_parent = 'noparent' in feature_value no_parent = 'noparent' in feature_value
sep = feature_name.rfind('.') sep = feature_name.rfind('.')
@@ -889,8 +920,9 @@ class FeatureCompiler(object):
parse_and_validate(feature_name, v, parent, shared_values)) parse_and_validate(feature_name, v, parent, shared_values))
self._features[feature_name] = feature self._features[feature_name] = feature
else: else:
self._features[feature_name] = parse_and_validate( self._features[feature_name] = parse_and_validate(feature_name,
feature_name, feature_value, parent, shared_values) feature_value, parent,
shared_values)
# Apply parent shared values at the end to enable child features to # Apply parent shared values at the end to enable child features to
# override parent shared value - if parent shared values are added to # override parent shared value - if parent shared values are added to
@@ -926,7 +958,8 @@ class FeatureCompiler(object):
required_buildflags = feature.GetValue('required_buildflags') required_buildflags = feature.GetValue('required_buildflags')
if required_buildflags: if required_buildflags:
formatted_buildflags = [ formatted_buildflags = [
'BUILDFLAG(%s)' % format(flag.upper()) for flag in required_buildflags 'BUILDFLAG(%s)' % format(flag.upper())
for flag in required_buildflags
] ]
c.Append('#if %s' % format(' && '.join(formatted_buildflags))) c.Append('#if %s' % format(' && '.join(formatted_buildflags)))
c.Concat(feature.GetCode(self._feature_type)) c.Concat(feature.GetCode(self._feature_type))
@@ -942,16 +975,20 @@ class FeatureCompiler(object):
header_file = self._out_base_filename + '.h' header_file = self._out_base_filename + '.h'
cc_file = self._out_base_filename + '.cc' cc_file = self._out_base_filename + '.cc'
include_file_root = self._out_root[len(self._gen_dir_relpath)+1:] include_file_root = self._out_root[len(self._gen_dir_relpath) + 1:]
header_file_path = '%s/%s' % (include_file_root, header_file) header_file_path = '%s/%s' % (include_file_root, header_file)
cc_file_path = '%s/%s' % (include_file_root, cc_file) cc_file_path = '%s/%s' % (include_file_root, cc_file)
substitutions = ({ substitutions = ({
'header_file_path': header_file_path, 'header_file_path':
'header_guard': (header_file_path.replace('/', '_'). header_file_path,
replace('.', '_').upper()), 'header_guard':
'method_name': self._method_name, (header_file_path.replace('/', '_').replace('.', '_').upper()),
'source_files': str([ToPosixPath(f) for f in self._source_files]), 'method_name':
'year': str(datetime.now().year) self._method_name,
'source_files':
str([ToPosixPath(f) for f in self._source_files]),
'year':
str(datetime.now().year)
}) })
if not os.path.exists(self._out_root): if not os.path.exists(self._out_root):
os.makedirs(self._out_root) os.makedirs(self._out_root)
@@ -973,25 +1010,36 @@ class FeatureCompiler(object):
cc_file.Concat(cc_end) cc_file.Concat(cc_end)
f.write(cc_file.Render().strip()) f.write(cc_file.Render().strip())
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Compile json feature files') parser = argparse.ArgumentParser(description='Compile json feature files')
parser.add_argument('chrome_root', type=str, parser.add_argument('chrome_root',
type=str,
help='The root directory of the chrome checkout') help='The root directory of the chrome checkout')
parser.add_argument( parser.add_argument(
'feature_type', type=str, 'feature_type',
type=str,
help='The name of the class to use in feature generation ' + help='The name of the class to use in feature generation ' +
'(e.g. APIFeature, PermissionFeature)') '(e.g. APIFeature, PermissionFeature)')
parser.add_argument('method_name', type=str, parser.add_argument('method_name',
type=str,
help='The name of the method to populate the provider') help='The name of the method to populate the provider')
parser.add_argument('out_root', type=str, parser.add_argument('out_root',
type=str,
help='The root directory to generate the C++ files into') help='The root directory to generate the C++ files into')
parser.add_argument('gen_dir_relpath', default='gen', help='Path of the ' parser.add_argument(
'gen_dir_relpath',
default='gen',
help='Path of the '
'gen directory relative to the out/. If running in the default ' 'gen directory relative to the out/. If running in the default '
'toolchain, the path is gen, otherwise $toolchain_name/gen') 'toolchain, the path is gen, otherwise $toolchain_name/gen')
parser.add_argument( parser.add_argument(
'out_base_filename', type=str, 'out_base_filename',
type=str,
help='The base filename for the C++ files (.h and .cc will be appended)') help='The base filename for the C++ files (.h and .cc will be appended)')
parser.add_argument('source_files', type=str, nargs='+', parser.add_argument('source_files',
type=str,
nargs='+',
help='The source features.json files') help='The source features.json files')
args = parser.parse_args() args = parser.parse_args()
if args.feature_type not in FEATURE_TYPES: if args.feature_type not in FEATURE_TYPES:

@@ -7,6 +7,7 @@ import copy
import feature_compiler import feature_compiler
import unittest import unittest
class FeatureCompilerTest(unittest.TestCase): class FeatureCompilerTest(unittest.TestCase):
"""Test the FeatureCompiler. Note that we test that the expected features are """Test the FeatureCompiler. Note that we test that the expected features are
generated more thoroughly in features_generation_unittest.cc. And, of course, generated more thoroughly in features_generation_unittest.cc. And, of course,
@@ -14,6 +15,7 @@ class FeatureCompilerTest(unittest.TestCase):
feature fails to parse, the compile fails). feature fails to parse, the compile fails).
These tests primarily focus on catching errors during parsing. These tests primarily focus on catching errors during parsing.
""" """
def _parseFeature(self, value): def _parseFeature(self, value):
"""Parses a feature from the given value and returns the result.""" """Parses a feature from the given value and returns the result."""
f = feature_compiler.Feature('alpha') f = feature_compiler.Feature('alpha')
@@ -22,7 +24,8 @@ class FeatureCompilerTest(unittest.TestCase):
def _createTestFeatureCompiler(self, feature_class): def _createTestFeatureCompiler(self, feature_class):
return feature_compiler.FeatureCompiler('chrome_root', [], feature_class, return feature_compiler.FeatureCompiler('chrome_root', [], feature_class,
'provider_class', 'out_root', 'gen', 'out_base_filename') 'provider_class', 'out_root', 'gen',
'out_base_filename')
def _hasError(self, f, error): def _hasError(self, f, error):
"""Asserts that |error| is present somewhere in the given feature's """Asserts that |error| is present somewhere in the given feature's
@@ -37,60 +40,69 @@ class FeatureCompilerTest(unittest.TestCase):
def testFeature(self): def testFeature(self):
# Test some basic feature parsing for a sanity check. # Test some basic feature parsing for a sanity check.
f = self._parseFeature({ f = self._parseFeature({
'blocklist': [ 'blocklist': [
'ABCDEF0123456789ABCDEF0123456789ABCDEF01', 'ABCDEF0123456789ABCDEF0123456789ABCDEF01',
'10FEDCBA9876543210FEDCBA9876543210FEDCBA' '10FEDCBA9876543210FEDCBA9876543210FEDCBA'
], ],
'channel': 'stable', 'channel':
'command_line_switch': 'switch', 'stable',
'component_extensions_auto_granted': False, 'command_line_switch':
'contexts': [ 'switch',
'privileged_extension', 'component_extensions_auto_granted':
'privileged_web_page', False,
'lock_screen_extension' 'contexts': [
], 'privileged_extension', 'privileged_web_page',
'default_parent': True, 'lock_screen_extension'
'dependencies': ['dependency1', 'dependency2'], ],
'developer_mode_only': True, 'default_parent':
'disallow_for_service_workers': True, True,
'extension_types': ['extension'], 'dependencies': ['dependency1', 'dependency2'],
'location': 'component', 'developer_mode_only':
'internal': True, True,
'matches': ['*://*/*'], 'disallow_for_service_workers':
'max_manifest_version': 1, True,
'requires_delegated_availability_check': True, 'extension_types': ['extension'],
'noparent': True, 'location':
'platforms': ['mac', 'win'], 'component',
'session_types': ['kiosk', 'regular'], 'internal':
'allowlist': [ True,
'0123456789ABCDEF0123456789ABCDEF01234567', 'matches': ['*://*/*'],
'76543210FEDCBA9876543210FEDCBA9876543210' 'max_manifest_version':
], 1,
'required_buildflags': [ 'requires_delegated_availability_check':
'use_cups' True,
] 'noparent':
True,
'platforms': ['mac', 'win'],
'session_types': ['kiosk', 'regular'],
'allowlist': [
'0123456789ABCDEF0123456789ABCDEF01234567',
'76543210FEDCBA9876543210FEDCBA9876543210'
],
'required_buildflags': ['use_cups']
}) })
self.assertFalse(f.GetErrors()) self.assertFalse(f.GetErrors())
def testInvalidAll(self): def testInvalidAll(self):
f = self._parseFeature({ f = self._parseFeature({
'channel': 'stable', 'channel': 'stable',
'dependencies': 'all', 'dependencies': 'all',
}) })
self._hasError(f, 'Illegal value: "all"') self._hasError(f, 'Illegal value: "all"')
def testUnknownKeyError(self): def testUnknownKeyError(self):
f = self._parseFeature({ f = self._parseFeature({
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'stable', 'channel': 'stable',
'unknownkey': 'unknownvalue' 'unknownkey': 'unknownvalue'
}) })
self._hasError(f, 'Unrecognized key') self._hasError(f, 'Unrecognized key')
def testUnknownEnumValue(self): def testUnknownEnumValue(self):
f = self._parseFeature({ f = self._parseFeature({
'contexts': ['privileged_extension', 'unknown_context'], 'contexts': ['privileged_extension', 'unknown_context'],
'channel': 'stable' 'channel':
'stable'
}) })
self._hasError(f, 'Illegal value: "unknown_context"') self._hasError(f, 'Illegal value: "unknown_context"')
@@ -116,15 +128,19 @@ class FeatureCompilerTest(unittest.TestCase):
self.assertFalse(f.GetErrors()) self.assertFalse(f.GetErrors())
def testApiFeaturesNeedContexts(self): def testApiFeaturesNeedContexts(self):
f = self._parseFeature({'extension_types': ['extension'], f = self._parseFeature({
'channel': 'trunk'}) 'extension_types': ['extension'],
'channel': 'trunk'
})
f.Validate('APIFeature', {}) f.Validate('APIFeature', {})
self._hasError(f, 'APIFeatures must specify the contexts property') self._hasError(f, 'APIFeatures must specify the contexts property')
def testAPIFeaturesCanSpecifyEmptyContexts(self): def testAPIFeaturesCanSpecifyEmptyContexts(self):
f = self._parseFeature({'extension_types': ['extension'], f = self._parseFeature({
'channel': 'trunk', 'extension_types': ['extension'],
'contexts': []}) 'channel': 'trunk',
'contexts': []
})
f.Validate('APIFeature', {}) f.Validate('APIFeature', {})
self.assertFalse(f.GetErrors()) self.assertFalse(f.GetErrors())
@@ -135,10 +151,12 @@ class FeatureCompilerTest(unittest.TestCase):
'ManifestFeatures must specify at least one extension type') 'ManifestFeatures must specify at least one extension type')
def testManifestFeaturesCantHaveContexts(self): def testManifestFeaturesCantHaveContexts(self):
f = self._parseFeature({'dependencies': 'alpha', f = self._parseFeature({
'channel': 'beta', 'dependencies': 'alpha',
'extension_types': ['extension'], 'channel': 'beta',
'contexts': ['privileged_extension']}) 'extension_types': ['extension'],
'contexts': ['privileged_extension']
})
f.Validate('ManifestFeature', {}) f.Validate('ManifestFeature', {})
self._hasError(f, 'ManifestFeatures do not support contexts') self._hasError(f, 'ManifestFeatures do not support contexts')
@@ -149,18 +167,20 @@ class FeatureCompilerTest(unittest.TestCase):
f, 'PermissionFeatures must specify at least one extension type') f, 'PermissionFeatures must specify at least one extension type')
def testPermissionFeaturesCantHaveContexts(self): def testPermissionFeaturesCantHaveContexts(self):
f = self._parseFeature({'dependencies': 'alpha', f = self._parseFeature({
'channel': 'beta', 'dependencies': 'alpha',
'extension_types': ['extension'], 'channel': 'beta',
'contexts': ['privileged_extension']}) 'extension_types': ['extension'],
'contexts': ['privileged_extension']
})
f.Validate('PermissionFeature', {}) f.Validate('PermissionFeature', {})
self._hasError(f, 'PermissionFeatures do not support contexts') self._hasError(f, 'PermissionFeatures do not support contexts')
def testAllPermissionsNeedChannelOrDependencies(self): def testAllPermissionsNeedChannelOrDependencies(self):
api_feature = self._parseFeature({'contexts': ['privileged_extension']}) api_feature = self._parseFeature({'contexts': ['privileged_extension']})
api_feature.Validate('APIFeature', {}) api_feature.Validate('APIFeature', {})
self._hasError( self._hasError(api_feature,
api_feature, 'Features must specify either a channel or dependencies') 'Features must specify either a channel or dependencies')
permission_feature = self._parseFeature({'extension_types': ['extension']}) permission_feature = self._parseFeature({'extension_types': ['extension']})
permission_feature.Validate('PermissionFeature', {}) permission_feature.Validate('PermissionFeature', {})
self._hasError(permission_feature, self._hasError(permission_feature,
@@ -169,25 +189,28 @@ class FeatureCompilerTest(unittest.TestCase):
manifest_feature.Validate('ManifestFeature', {}) manifest_feature.Validate('ManifestFeature', {})
self._hasError(manifest_feature, self._hasError(manifest_feature,
'Features must specify either a channel or dependencies') 'Features must specify either a channel or dependencies')
channel_feature = self._parseFeature({'contexts': ['privileged_extension'], channel_feature = self._parseFeature({
'channel': 'trunk'}) 'contexts': ['privileged_extension'],
'channel': 'trunk'
})
channel_feature.Validate('APIFeature', {}) channel_feature.Validate('APIFeature', {})
self.assertFalse(channel_feature.GetErrors()) self.assertFalse(channel_feature.GetErrors())
dependency_feature = self._parseFeature( dependency_feature = self._parseFeature({
{'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'dependencies': ['alpha']}) 'dependencies': ['alpha']
})
dependency_feature.Validate('APIFeature', {}) dependency_feature.Validate('APIFeature', {})
self.assertFalse(dependency_feature.GetErrors()) self.assertFalse(dependency_feature.GetErrors())
def testBothAliasAndSource(self): def testBothAliasAndSource(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': { 'feature_alpha': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_alpha', 'alias': 'feature_alpha',
'source': 'feature_alpha' 'source': 'feature_alpha'
} }
} }
compiler.Compile() compiler.Compile()
@@ -198,20 +221,20 @@ class FeatureCompilerTest(unittest.TestCase):
def testAliasOnNonApiFeature(self): def testAliasOnNonApiFeature(self):
compiler = self._createTestFeatureCompiler('PermissionFeature') compiler = self._createTestFeatureCompiler('PermissionFeature')
compiler._json = { compiler._json = {
'feature_alpha': { 'feature_alpha': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_beta' 'alias': 'feature_beta'
}, },
'feature_beta': [{ 'feature_beta': [{
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'source': 'feature_alpha' 'source': 'feature_alpha'
},{ }, {
'channel': 'dev', 'channel': 'dev',
'context': ['privileged_extension'] 'context': ['privileged_extension']
}] }]
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
@@ -225,17 +248,17 @@ class FeatureCompilerTest(unittest.TestCase):
def testAliasFeature(self): def testAliasFeature(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': { 'feature_alpha': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_beta' 'alias': 'feature_beta'
}, },
'feature_beta': { 'feature_beta': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'source': 'feature_alpha' 'source': 'feature_alpha'
} }
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
@@ -249,40 +272,41 @@ class FeatureCompilerTest(unittest.TestCase):
def testMultipleAliasesInComplexFeature(self): def testMultipleAliasesInComplexFeature(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': [{ 'feature_alpha': [{
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_beta' 'alias': 'feature_beta'
}, { }, {
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'beta', 'channel': 'beta',
'alias': 'feature_beta' 'alias': 'feature_beta'
}] }]
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
self.assertTrue(feature) self.assertTrue(feature)
self._hasError(feature, 'Error parsing feature "feature_alpha" at key ' + self._hasError(
'"alias": Key can be set at most once per feature.') feature, 'Error parsing feature "feature_alpha" at key ' +
'"alias": Key can be set at most once per feature.')
def testAliasReferenceInComplexFeature(self): def testAliasReferenceInComplexFeature(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': [{ 'feature_alpha': [{
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_beta' 'alias': 'feature_beta'
}, { }, {
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'beta', 'channel': 'beta',
}], }],
'feature_beta': { 'feature_beta': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'source': 'feature_alpha' 'source': 'feature_alpha'
} }
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
@@ -296,57 +320,58 @@ class FeatureCompilerTest(unittest.TestCase):
def testSourceMissingReference(self): def testSourceMissingReference(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': { 'feature_alpha': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_beta' 'alias': 'feature_beta'
}, },
'feature_beta': { 'feature_beta': {
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'beta', 'channel': 'beta',
'source': 'does_not_exist' 'source': 'does_not_exist'
} }
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_beta') feature = compiler._features.get('feature_beta')
self.assertTrue(feature) self.assertTrue(feature)
self._hasError(feature, 'A feature source property should reference a ' + self._hasError(
'feature whose alias property references it back.') feature, 'A feature source property should reference a ' +
'feature whose alias property references it back.')
def testAliasMissingReferenceInComplexFeature(self): def testAliasMissingReferenceInComplexFeature(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': [{ 'feature_alpha': [{
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_beta' 'alias': 'feature_beta'
}, { }, {
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'beta' 'channel': 'beta'
}] }]
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
self.assertTrue(feature) self.assertTrue(feature)
self._hasError(feature, 'A feature alias property should reference a ' + self._hasError(
'feature whose source property references it back.') feature, 'A feature alias property should reference a ' +
'feature whose source property references it back.')
def testAliasReferenceMissingSourceInComplexFeature(self): def testAliasReferenceMissingSourceInComplexFeature(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': { 'feature_alpha': {
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'beta', 'channel': 'beta',
}, },
'feature_beta': { 'feature_beta': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'alias': 'feature_alpha' 'alias': 'feature_alpha'
} }
}; }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
@@ -355,23 +380,23 @@ class FeatureCompilerTest(unittest.TestCase):
feature = compiler._features.get('feature_beta') feature = compiler._features.get('feature_beta')
self.assertTrue(feature) self.assertTrue(feature)
self._hasError(feature, 'A feature alias property should reference a ' + self._hasError(
'feature whose source property references it back.') feature, 'A feature alias property should reference a ' +
'feature whose source property references it back.')
def testComplexParentWithoutDefaultParent(self): def testComplexParentWithoutDefaultParent(self):
c = feature_compiler.FeatureCompiler( c = feature_compiler.FeatureCompiler(None, None, 'APIFeature', None, None,
None, None, 'APIFeature', None, None, None, None) None, None)
c._CompileFeature('bookmarks', c._CompileFeature('bookmarks', [{
[{ 'contexts': ['privileged_extension'],
'contexts': ['privileged_extension'], }, {
}, { 'channel': 'stable',
'channel': 'stable', 'contexts': ['webui'],
'contexts': ['webui'], }])
}])
with self.assertRaisesRegex(AssertionError, with self.assertRaisesRegex(AssertionError,
'No default parent found for bookmarks'): 'No default parent found for bookmarks'):
c._CompileFeature('bookmarks.export', { "allowlist": ["asdf"] }) c._CompileFeature('bookmarks.export', {"allowlist": ["asdf"]})
def testComplexFeatureWithSinglePropertyBlock(self): def testComplexFeatureWithSinglePropertyBlock(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
@@ -383,16 +408,18 @@ class FeatureCompilerTest(unittest.TestCase):
'feature key instead of a list.') 'feature key instead of a list.')
with self.assertRaisesRegex(AssertionError, error): with self.assertRaisesRegex(AssertionError, error):
compiler._CompileFeature('feature_alpha', compiler._CompileFeature('feature_alpha',
[{ [{
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'channel': 'stable', 'channel': 'stable',
}]) }])
def testRealIdsDisallowedInAllowlist(self): def testRealIdsDisallowedInAllowlist(self):
fake_id = 'a' * 32; fake_id = 'a' * 32
f = self._parseFeature({'allowlist': [fake_id], f = self._parseFeature({
'extension_types': ['extension'], 'allowlist': [fake_id],
'channel': 'beta'}) 'extension_types': ['extension'],
'channel': 'beta'
})
f.Validate('PermissionFeature', {}) f.Validate('PermissionFeature', {})
self._hasError( self._hasError(
f, 'list should only have hex-encoded SHA1 hashes of extension ids') f, 'list should only have hex-encoded SHA1 hashes of extension ids')
@@ -401,31 +428,34 @@ class FeatureCompilerTest(unittest.TestCase):
f = self._parseFeature({ f = self._parseFeature({
'extension_types': ['extension', 'hosted_app'], 'extension_types': ['extension', 'hosted_app'],
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'], 'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
'channel': 'beta', 'channel':
'beta',
}) })
f.Validate('PermissionFeature', {}) f.Validate('PermissionFeature', {})
self._hasError(f, 'Hosted apps are not allowed to use restricted features') self._hasError(f, 'Hosted apps are not allowed to use restricted features')
def testHostedAppsCantUseAllowlistedFeatures_ComplexFeature(self): def testHostedAppsCantUseAllowlistedFeatures_ComplexFeature(self):
c = feature_compiler.FeatureCompiler( c = feature_compiler.FeatureCompiler(None, None, 'PermissionFeature', None,
None, None, 'PermissionFeature', None, None, None, None) None, None, None)
c._CompileFeature('invalid_feature', c._CompileFeature(
'invalid_feature',
[{ [{
'extension_types': ['extension'], 'extension_types': ['extension'],
'channel': 'beta', 'channel': 'beta',
}, { }, {
'channel': 'beta', 'channel': 'beta',
'extension_types': ['hosted_app'], 'extension_types': ['hosted_app'],
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'], 'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
}]) }])
c._CompileFeature('valid_feature', c._CompileFeature(
'valid_feature',
[{ [{
'extension_types': ['extension'], 'extension_types': ['extension'],
'channel': 'beta', 'channel': 'beta',
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'], 'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
}, { }, {
'channel': 'beta', 'channel': 'beta',
'extension_types': ['hosted_app'], 'extension_types': ['hosted_app'],
}]) }])
valid_feature = c._features.get('valid_feature') valid_feature = c._features.get('valid_feature')
@@ -437,20 +467,17 @@ class FeatureCompilerTest(unittest.TestCase):
self._hasError(invalid_feature, self._hasError(invalid_feature,
'Hosted apps are not allowed to use restricted features') 'Hosted apps are not allowed to use restricted features')
def testHostedAppsCantUseAllowlistedFeatures_ChildFeature(self): def testHostedAppsCantUseAllowlistedFeatures_ChildFeature(self):
c = feature_compiler.FeatureCompiler( c = feature_compiler.FeatureCompiler(None, None, 'PermissionFeature', None,
None, None, 'PermissionFeature', None, None, None, None) None, None, None)
c._CompileFeature('parent', c._CompileFeature('parent', {
{ 'extension_types': ['hosted_app'],
'extension_types': ['hosted_app'], 'channel': 'beta',
'channel': 'beta', })
})
c._CompileFeature('parent.child', c._CompileFeature(
{ 'parent.child',
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'] {'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567']})
})
feature = c._features.get('parent.child') feature = c._features.get('parent.child')
self.assertTrue(feature) self.assertTrue(feature)
self._hasError(feature, self._hasError(feature,
@@ -459,27 +486,27 @@ class FeatureCompilerTest(unittest.TestCase):
def testEmptyContextsDisallowed(self): def testEmptyContextsDisallowed(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_alpha': { 'feature_alpha': {
'channel': 'beta', 'channel': 'beta',
'contexts': [], 'contexts': [],
'extension_types': ['extension'] 'extension_types': ['extension']
} }
} }
compiler.Compile() compiler.Compile()
feature = compiler._features.get('feature_alpha') feature = compiler._features.get('feature_alpha')
self.assertTrue(feature) self.assertTrue(feature)
self._hasError(feature, self._hasError(feature,
'An empty contexts list is not allowed for this feature.') 'An empty contexts list is not allowed for this feature.')
def testEmptyContextsAllowed(self): def testEmptyContextsAllowed(self):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'empty_contexts': { 'empty_contexts': {
'channel': 'beta', 'channel': 'beta',
'contexts': [], 'contexts': [],
'extension_types': ['extension'] 'extension_types': ['extension']
} }
} }
compiler.Compile() compiler.Compile()
@@ -491,18 +518,19 @@ class FeatureCompilerTest(unittest.TestCase):
compiler = self._createTestFeatureCompiler('APIFeature') compiler = self._createTestFeatureCompiler('APIFeature')
compiler._json = { compiler._json = {
'feature_cups': { 'feature_cups': {
'channel': 'beta', 'channel': 'beta',
'contexts': ['privileged_extension'], 'contexts': ['privileged_extension'],
'extension_types': ['extension'], 'extension_types': ['extension'],
'required_buildflags': ['use_cups'] 'required_buildflags': ['use_cups']
} }
} }
compiler.Compile() compiler.Compile()
cc_code = compiler.Render() cc_code = compiler.Render()
# The code below is formatted correctly! # The code below is formatted correctly!
self.assertEqual(cc_code.Render(), ''' { self.assertEqual(
cc_code.Render(), ''' {
#if BUILDFLAG(USE_CUPS) #if BUILDFLAG(USE_CUPS)
SimpleFeature* feature = new SimpleFeature(); SimpleFeature* feature = new SimpleFeature();
feature->set_name("feature_cups"); feature->set_name("feature_cups");
@@ -513,5 +541,6 @@ class FeatureCompilerTest(unittest.TestCase):
#endif #endif
}''') }''')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@@ -9,6 +9,7 @@ import cpp_util
class CCGenerator(object): class CCGenerator(object):
def Generate(self, feature_defs, source_file, namespace): def Generate(self, feature_defs, source_file, namespace):
return _Generator(feature_defs, source_file, namespace).Generate() return _Generator(feature_defs, source_file, namespace).Generate()
@@ -16,6 +17,7 @@ class CCGenerator(object):
class _Generator(object): class _Generator(object):
"""A .cc generator for features. """A .cc generator for features.
""" """
def __init__(self, feature_defs, source_file, namespace): def __init__(self, feature_defs, source_file, namespace):
self._feature_defs = feature_defs self._feature_defs = feature_defs
self._source_file = source_file self._source_file = source_file
@@ -27,54 +29,53 @@ class _Generator(object):
"""Generates a Code object for features. """Generates a Code object for features.
""" """
c = Code() c = Code()
(c.Append(cpp_util.CHROMIUM_LICENSE) (c.Append(cpp_util.CHROMIUM_LICENSE) \
.Append() .Append() \
.Append(cpp_util.GENERATED_FEATURE_MESSAGE % .Append(cpp_util.GENERATED_FEATURE_MESSAGE %
cpp_util.ToPosixPath(self._source_file)) cpp_util.ToPosixPath(self._source_file)) \
.Append() .Append() \
.Append('#include <string>') .Append('#include <string>') \
.Append() .Append() \
.Append('#include "%s.h"' % .Append('#include "%s.h"' %
cpp_util.ToPosixPath(self._source_file_filename)) cpp_util.ToPosixPath(self._source_file_filename)) \
.Append() .Append() \
.Append('#include "base/notreached.h"') .Append('#include "base/notreached.h"') \
.Append() .Append() \
.Concat(cpp_util.OpenNamespace(self._namespace)) .Concat(cpp_util.OpenNamespace(self._namespace)) \
.Append() .Append()
) )
# Generate the constructor. # Generate the constructor.
(c.Append('%s::%s() {' % (self._class_name, self._class_name)) (c.Append('%s::%s() {' % (self._class_name, self._class_name)) \
.Sblock() .Sblock()
) )
for feature in self._feature_defs: for feature in self._feature_defs:
c.Append('features_["%s"] = %s;' c.Append('features_["%s"] = %s;' %
% (feature.name, (feature.name, cpp_util.FeatureNameToConstantName(feature.name)))
cpp_util.FeatureNameToConstantName(feature.name))) (c.Eblock() \
(c.Eblock() .Append('}') \
.Append('}')
.Append() .Append()
) )
# Generate the ToString function. # Generate the ToString function.
(c.Append('const char* %s::ToString(' (c.Append('const char* %s::ToString('
'%s::ID id) const {' % (self._class_name, self._class_name)) '%s::ID id) const {' % (self._class_name, self._class_name)) \
.Sblock() .Sblock() \
.Append('switch (id) {') .Append('switch (id) {') \
.Sblock() .Sblock()
) )
for feature in self._feature_defs: for feature in self._feature_defs:
c.Append('case %s: return "%s";' % c.Append('case %s: return "%s";' %
(cpp_util.FeatureNameToConstantName(feature.name), feature.name)) (cpp_util.FeatureNameToConstantName(feature.name), feature.name))
(c.Append('case kUnknown: break;') (c.Append('case kUnknown: break;') \
.Append('case kEnumBoundary: break;') .Append('case kEnumBoundary: break;') \
.Eblock() .Eblock() \
.Append('}') .Append('}') \
.Append('NOTREACHED_IN_MIGRATION();') .Append('NOTREACHED_IN_MIGRATION();') \
.Append('return "";') .Append('return "";')
) )
(c.Eblock() (c.Eblock() \
.Append('}') .Append('}') \
.Append() .Append()
) )
@@ -82,13 +83,13 @@ class _Generator(object):
(c.Append('%s::ID %s::FromString(' (c.Append('%s::ID %s::FromString('
'const std::string& id) const {' 'const std::string& id) const {'
% (self._class_name, self._class_name)) % (self._class_name, self._class_name)) \
.Sblock() .Sblock() \
.Append('const auto& it = features_.find(id);' % self._class_name) .Append('const auto& it = features_.find(id);' % self._class_name) \
.Append('return (it == features_.end()) ? kUnknown : it->second;') .Append('return (it == features_.end()) ? kUnknown : it->second;') \
.Eblock() .Eblock() \
.Append('}') .Append('}') \
.Append() .Append() \
.Cblock(cpp_util.CloseNamespace(self._namespace)) .Cblock(cpp_util.CloseNamespace(self._namespace))
) )

@@ -23,9 +23,7 @@ def _GenerateSchema(filename, root, destdir, namespace):
# Load in the feature permissions from the JSON file. # Load in the feature permissions from the JSON file.
schema = os.path.normpath(filename) schema = os.path.normpath(filename)
schema_loader = SchemaLoader(os.path.dirname(os.path.relpath(schema, root)), schema_loader = SchemaLoader(os.path.dirname(os.path.relpath(schema, root)),
os.path.dirname(schema), os.path.dirname(schema), [], None)
[],
None)
schema_filename = os.path.splitext(schema)[0] schema_filename = os.path.splitext(schema)[0]
feature_defs = schema_loader.LoadSchema(schema) feature_defs = schema_loader.LoadSchema(schema)
@@ -38,10 +36,8 @@ def _GenerateSchema(filename, root, destdir, namespace):
relpath = os.path.relpath(os.path.normpath(source_file_dir), root) relpath = os.path.relpath(os.path.normpath(source_file_dir), root)
full_path = os.path.join(relpath, schema) full_path = os.path.join(relpath, schema)
generators = [ generators = [('%s.cc' % schema_filename, CCGenerator()),
('%s.cc' % schema_filename, CCGenerator()), ('%s.h' % schema_filename, HGenerator())]
('%s.h' % schema_filename, HGenerator())
]
# Generate and output the code for all features. # Generate and output the code for all features.
output_code = [] output_code = []
@@ -59,12 +55,19 @@ if __name__ == '__main__':
parser = optparse.OptionParser( parser = optparse.OptionParser(
description='Generates a C++ features model from JSON schema', description='Generates a C++ features model from JSON schema',
usage='usage: %prog [option]... schema') usage='usage: %prog [option]... schema')
parser.add_option('-r', '--root', default='.', parser.add_option(
'-r',
'--root',
default='.',
help='logical include root directory. Path to schema files from ' help='logical include root directory. Path to schema files from '
'specified dir will be the include path.') 'specified dir will be the include path.')
parser.add_option('-d', '--destdir', parser.add_option('-d',
help='root directory to output generated files.') '--destdir',
parser.add_option('-n', '--namespace', default='generated_features', help='root directory to output generated files.')
parser.add_option(
'-n',
'--namespace',
default='generated_features',
help='C++ namespace for generated files. e.g extensions::api.') help='C++ namespace for generated files. e.g extensions::api.')
(opts, filenames) = parser.parse_args() (opts, filenames) = parser.parse_args()

@@ -9,6 +9,7 @@ import cpp_util
class HGenerator(object): class HGenerator(object):
def Generate(self, features, source_file, namespace): def Generate(self, features, source_file, namespace):
return _Generator(features, source_file, namespace).Generate() return _Generator(features, source_file, namespace).Generate()
@@ -16,6 +17,7 @@ class HGenerator(object):
class _Generator(object): class _Generator(object):
"""A .cc generator for features. """A .cc generator for features.
""" """
def __init__(self, features, source_file, namespace): def __init__(self, features, source_file, namespace):
self._feature_defs = features self._feature_defs = features
self._source_file = source_file self._source_file = source_file
@@ -27,10 +29,10 @@ class _Generator(object):
"""Generates a Code object for features. """Generates a Code object for features.
""" """
c = Code() c = Code()
(c.Append(cpp_util.CHROMIUM_LICENSE) (c.Append(cpp_util.CHROMIUM_LICENSE) \
.Append() .Append() \
.Append(cpp_util.GENERATED_FEATURE_MESSAGE % .Append(cpp_util.GENERATED_FEATURE_MESSAGE %
cpp_util.ToPosixPath(self._source_file)) cpp_util.ToPosixPath(self._source_file)) \
.Append() .Append()
) )
@@ -39,31 +41,31 @@ class _Generator(object):
output_file = os.path.splitext(self._namespace.source_file)[0] + '.h' output_file = os.path.splitext(self._namespace.source_file)[0] + '.h'
ifndef_name = cpp_util.GenerateIfndefName(output_file) ifndef_name = cpp_util.GenerateIfndefName(output_file)
(c.Append('#ifndef %s' % ifndef_name) (c.Append('#ifndef %s' % ifndef_name) \
.Append('#define %s' % ifndef_name) .Append('#define %s' % ifndef_name) \
.Append() .Append()
) )
(c.Append('#include <map>') (c.Append('#include <map>') \
.Append('#include <string>') .Append('#include <string>') \
.Append() .Append() \
.Concat(cpp_util.OpenNamespace(self._namespace)) .Concat(cpp_util.OpenNamespace(self._namespace)) \
.Append() .Append()
) )
(c.Append('class %s {' % self._class_name) (c.Append('class %s {' % self._class_name) \
.Append(' public:') .Append(' public:') \
.Sblock() .Sblock() \
.Concat(self._GeneratePublicBody()) .Concat(self._GeneratePublicBody()) \
.Eblock() .Eblock() \
.Append(' private:') .Append(' private:') \
.Sblock() .Sblock() \
.Concat(self._GeneratePrivateBody()) .Concat(self._GeneratePrivateBody()) \
.Eblock('};') .Eblock('};') \
.Append() .Append() \
.Cblock(cpp_util.CloseNamespace(self._namespace)) .Cblock(cpp_util.CloseNamespace(self._namespace))
) )
(c.Append('#endif // %s' % ifndef_name) (c.Append('#endif // %s' % ifndef_name) \
.Append() .Append()
) )
return c return c
@@ -71,14 +73,14 @@ class _Generator(object):
def _GeneratePublicBody(self): def _GeneratePublicBody(self):
c = Code() c = Code()
(c.Append('%s();' % self._class_name) (c.Append('%s();' % self._class_name) \
.Append() .Append() \
.Append('enum ID {') .Append('enum ID {') \
.Concat(self._GenerateEnumConstants()) .Concat(self._GenerateEnumConstants()) \
.Eblock('};') .Eblock('};') \
.Append() .Append() \
.Append('const char* ToString(ID id) const;') .Append('const char* ToString(ID id) const;') \
.Append('ID FromString(const std::string& id) const;') .Append('ID FromString(const std::string& id) const;') \
.Append() .Append()
) )
return c return c
@@ -90,7 +92,7 @@ class _Generator(object):
def _GenerateEnumConstants(self): def _GenerateEnumConstants(self):
c = Code() c = Code()
(c.Sblock() (c.Sblock() \
.Append('kUnknown,') .Append('kUnknown,')
) )
for feature in self._feature_defs: for feature in self._feature_defs:

@@ -2,7 +2,6 @@
# Copyright 2022 The Chromium Authors # Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Helper for quickly generating all known JS externs.""" """Helper for quickly generating all known JS externs."""
import argparse import argparse
@@ -31,6 +30,7 @@ REPO_ROOT = os.path.dirname(os.path.dirname(DIR))
# Import the helper module. # Import the helper module.
sys.path.insert(0, os.path.join(REPO_ROOT, 'extensions', 'common', 'api')) sys.path.insert(0, os.path.join(REPO_ROOT, 'extensions', 'common', 'api'))
from externs_checker import ExternsChecker from externs_checker import ExternsChecker
sys.path.pop(0) sys.path.pop(0)
@@ -72,6 +72,7 @@ class FakeOutputApi:
""" """
class PresubmitResult: class PresubmitResult:
def __init__(self, msg, long_text=None): def __init__(self, msg, long_text=None):
self.msg = msg self.msg = msg
self.long_text = long_text self.long_text = long_text
@@ -112,7 +113,7 @@ def Generate(input_api, output_api, force=False, dryrun=False):
externs_relpath = input_api.os_path.relpath(externs, src_root) externs_relpath = input_api.os_path.relpath(externs, src_root)
print('\r' + ' ' * msg_len, end='\r') print('\r' + ' ' * msg_len, end='\r')
msg = 'Checking %s ...' % (source_relpath,) msg = 'Checking %s ...' % (source_relpath, )
msg_len = len(msg) msg_len = len(msg)
print(msg, end='') print(msg, end='')
sys.stdout.flush() sys.stdout.flush()
@@ -123,9 +124,9 @@ def Generate(input_api, output_api, force=False, dryrun=False):
if not dryrun: if not dryrun:
print('\n%s: %s' % (source_relpath, e)) print('\n%s: %s' % (source_relpath, e))
ret.append( ret.append(
output_api.PresubmitResult( output_api.PresubmitResult('%s: unable to generate' %
'%s: unable to generate' % (source_relpath,), (source_relpath, ),
long_text=str(e))) long_text=str(e)))
continue continue
# Ignore the first line (copyright) to avoid yearly thrashing. # Ignore the first line (copyright) to avoid yearly thrashing.
@@ -148,7 +149,7 @@ def Generate(input_api, output_api, force=False, dryrun=False):
if not dryrun: if not dryrun:
print('\r' + ' ' * msg_len, end='\r') print('\r' + ' ' * msg_len, end='\r')
msg_len = 0 msg_len = 0
print('Updating %s' % (externs_relpath,)) print('Updating %s' % (externs_relpath, ))
with open(externs, 'w', encoding='utf-8') as fp: with open(externs, 'w', encoding='utf-8') as fp:
fp.write(copyright + '\n') fp.write(copyright + '\n')
fp.write(new_data) fp.write(new_data)
@@ -161,11 +162,16 @@ def Generate(input_api, output_api, force=False, dryrun=False):
def get_parser(): def get_parser():
"""Get CLI parser.""" """Get CLI parser."""
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-n', '--dry-run', dest='dryrun', action='store_true', parser.add_argument('-n',
'--dry-run',
dest='dryrun',
action='store_true',
help="Don't make changes; only show changed files") help="Don't make changes; only show changed files")
parser.add_argument('-f', '--force', action='store_true', parser.add_argument('-f',
'--force',
action='store_true',
help='Regenerate files even if they have a TODO ' help='Regenerate files even if they have a TODO '
'disabling generation') 'disabling generation')
return parser return parser
@@ -174,7 +180,9 @@ def main(argv):
parser = get_parser() parser = get_parser()
opts = parser.parse_args(argv) opts = parser.parse_args(argv)
results = Generate(FakeInputApi(), FakeOutputApi(), force=opts.force, results = Generate(FakeInputApi(),
FakeOutputApi(),
force=opts.force,
dryrun=opts.dryrun) dryrun=opts.dryrun)
if opts.dryrun and results: if opts.dryrun and results:
for result in results: for result in results:

@@ -9,7 +9,9 @@ from model import PropertyType, Type, Property
import cpp_util import cpp_util
import schema_util import schema_util
class HGenerator(object): class HGenerator(object):
def __init__(self, type_generator): def __init__(self, type_generator):
self._type_generator = type_generator self._type_generator = type_generator
@@ -20,6 +22,7 @@ class HGenerator(object):
class _Generator(object): class _Generator(object):
"""A .h generator for a namespace. """A .h generator for a namespace.
""" """
def __init__(self, namespace, cpp_type_generator): def __init__(self, namespace, cpp_type_generator):
self._namespace = namespace self._namespace = namespace
self._type_helper = cpp_type_generator self._type_helper = cpp_type_generator
@@ -30,10 +33,10 @@ class _Generator(object):
"""Generates a Code object with the .h for a single namespace. """Generates a Code object with the .h for a single namespace.
""" """
c = Code() c = Code()
(c.Append(cpp_util.CHROMIUM_LICENSE) (c.Append(cpp_util.CHROMIUM_LICENSE) \
.Append() .Append() \
.Append(cpp_util.GENERATED_FILE_MESSAGE % .Append(cpp_util.GENERATED_FILE_MESSAGE %
cpp_util.ToPosixPath(self._namespace.source_file)) cpp_util.ToPosixPath(self._namespace.source_file)) \
.Append() .Append()
) )
@@ -48,21 +51,21 @@ class _Generator(object):
# non-optional types from other namespaces. # non-optional types from other namespaces.
include_soft = self._namespace.name not in ('tabs', 'windows') include_soft = self._namespace.name not in ('tabs', 'windows')
(c.Append('#ifndef %s' % ifndef_name) (c.Append('#ifndef %s' % ifndef_name) \
.Append('#define %s' % ifndef_name) .Append('#define %s' % ifndef_name) \
.Append() .Append() \
.Append('#include <stdint.h>') .Append('#include <stdint.h>') \
.Append() .Append() \
.Append('#include <map>') .Append('#include <map>') \
.Append('#include <memory>') .Append('#include <memory>') \
.Append('#include <optional>') .Append('#include <optional>') \
.Append('#include <string>') .Append('#include <string>') \
.Append('#include <vector>') .Append('#include <vector>') \
.Append() .Append() \
.Append('#include "base/values.h"') .Append('#include "base/values.h"') \
.Cblock(self._type_helper.GenerateIncludes( .Cblock(self._type_helper.GenerateIncludes(
include_soft=include_soft, include_soft=include_soft,
generate_error_messages=self._generate_error_messages)) generate_error_messages=self._generate_error_messages)) \
.Append() .Append()
) )
@@ -77,22 +80,21 @@ class _Generator(object):
c.Concat(cpp_util.OpenNamespace(cpp_namespace)) c.Concat(cpp_util.OpenNamespace(cpp_namespace))
c.Append() c.Append()
if self._namespace.properties: if self._namespace.properties:
(c.Append('//') (c.Append('//') \
.Append('// Properties') .Append('// Properties') \
.Append('//') .Append('//') \
.Append() .Append()
) )
for prop in self._namespace.properties.values(): for prop in self._namespace.properties.values():
property_code = self._type_helper.GeneratePropertyValues( property_code = self._type_helper.GeneratePropertyValues(
prop, prop, 'extern const %(type)s %(name)s;')
'extern const %(type)s %(name)s;')
if property_code: if property_code:
c.Cblock(property_code) c.Cblock(property_code)
if self._namespace.types: if self._namespace.types:
(c.Append('//') (c.Append('//') \
.Append('// Types') .Append('// Types') \
.Append('//') .Append('//') \
.Append() .Append() \
.Cblock(self._GenerateTypes(self._FieldDependencyOrder(), .Cblock(self._GenerateTypes(self._FieldDependencyOrder(),
is_toplevel=True, is_toplevel=True,
generate_typedefs=True)) generate_typedefs=True))
@@ -104,24 +106,24 @@ class _Generator(object):
c.Append() c.Append()
c.Cblock(self._GenerateManifestKeys()) c.Cblock(self._GenerateManifestKeys())
if self._namespace.functions: if self._namespace.functions:
(c.Append('//') (c.Append('//') \
.Append('// Functions') .Append('// Functions') \
.Append('//') .Append('//') \
.Append() .Append()
) )
for function in self._namespace.functions.values(): for function in self._namespace.functions.values():
c.Cblock(self._GenerateFunction(function)) c.Cblock(self._GenerateFunction(function))
if self._namespace.events: if self._namespace.events:
(c.Append('//') (c.Append('//') \
.Append('// Events') .Append('// Events') \
.Append('//') .Append('//') \
.Append() .Append()
) )
for event in self._namespace.events.values(): for event in self._namespace.events.values():
c.Cblock(self._GenerateEvent(event)) c.Cblock(self._GenerateEvent(event))
(c.Concat(cpp_util.CloseNamespace(cpp_namespace)) (c.Concat(cpp_util.CloseNamespace(cpp_namespace)) \
.Append() .Append() \
.Append('#endif // %s' % ifndef_name) .Append('#endif // %s' % ifndef_name) \
.Append() .Append()
) )
return c return c
@@ -137,8 +139,8 @@ class _Generator(object):
raise ValueError("Illegal circular dependency via cycle " + raise ValueError("Illegal circular dependency via cycle " +
", ".join(map(lambda x: x.name, path + [type_]))) ", ".join(map(lambda x: x.name, path + [type_])))
for prop in type_.properties.values(): for prop in type_.properties.values():
if (prop.type_ == PropertyType.REF and if (prop.type_ == PropertyType.REF and schema_util.GetNamespace(
schema_util.GetNamespace(prop.ref_type) == self._namespace.name): prop.ref_type) == self._namespace.name):
ExpandType(path + [type_], self._namespace.types[prop.ref_type]) ExpandType(path + [type_], self._namespace.types[prop.ref_type])
if not type_ in dependency_order: if not type_ in dependency_order:
dependency_order.append(type_) dependency_order.append(type_)
@@ -151,23 +153,23 @@ class _Generator(object):
"""Generate a code object with the declaration of a C++ enum. """Generate a code object with the declaration of a C++ enum.
""" """
c = Code() c = Code()
c.Sblock('enum class {name} {{'.format( c.Sblock('enum class {name} {{'.format(name=enum_name))
name=enum_name))
# Explicitly initialize kNone to 0, since we rely on default initialization # Explicitly initialize kNone to 0, since we rely on default initialization
# for enum members. Otherwise, default initialization will always set a # for enum members. Otherwise, default initialization will always set a
# value to 0, even if it's not a valid enum entry. # value to 0, even if it's not a valid enum entry.
c.Append( c.Append(
self._type_helper.GetEnumNoneValue(type_, full_name=False) + ' = 0,') self._type_helper.GetEnumNoneValue(type_, full_name=False) + ' = 0,')
for value in type_.enum_values: for value in type_.enum_values:
current_enum_string = ( current_enum_string = (self._type_helper.GetEnumValue(type_,
self._type_helper.GetEnumValue(type_, value, full_name=False)) value,
full_name=False))
c.Append(current_enum_string + ',') c.Append(current_enum_string + ',')
# Adding kMaxValue, which is friendly to enumaration histogram macros. # Adding kMaxValue, which is friendly to enumaration histogram macros.
c.Append('kMaxValue = {last_key_value},'.format( c.Append('kMaxValue = {last_key_value},'.format(
last_key_value=current_enum_string)) last_key_value=current_enum_string))
c.Eblock('};') c.Eblock('};')
return c return c
@@ -183,10 +185,8 @@ class _Generator(object):
needs_blank_line = True needs_blank_line = True
if prop.description: if prop.description:
c.Comment(prop.description) c.Comment(prop.description)
(c.Append('%s %s;' % ( (c.Append('%s %s;' % (self._type_helper.GetCppType(
self._type_helper.GetCppType(prop.type_, is_optional=prop.optional), prop.type_, is_optional=prop.optional), prop.unix_name)))
prop.unix_name))
)
return c return c
def _GenerateType(self, type_, is_toplevel=False, generate_typedefs=False): def _GenerateType(self, type_, is_toplevel=False, generate_typedefs=False):
@@ -204,7 +204,7 @@ class _Generator(object):
if type_.functions: if type_.functions:
# Wrap functions within types in the type's namespace. # Wrap functions within types in the type's namespace.
(c.Append('namespace %s {' % classname) (c.Append('namespace %s {' % classname) \
.Append() .Append()
) )
for function in type_.functions.values(): for function in type_.functions.values():
@@ -217,10 +217,8 @@ class _Generator(object):
if generate_typedefs: if generate_typedefs:
item_cpp_type = self._type_helper.GetCppType(type_.item_type) item_cpp_type = self._type_helper.GetCppType(type_.item_type)
if item_cpp_type != 'base::Value': if item_cpp_type != 'base::Value':
(c.Append('using %s = std::vector<%s >;' % ( (c.Append('using %s = std::vector<%s >;' %
classname, (classname, item_cpp_type)))
item_cpp_type))
)
else: else:
c.Append('using %s = base::Value::List;' % classname) c.Append('using %s = base::Value::List;' % classname)
elif type_.property_type == PropertyType.STRING: elif type_.property_type == PropertyType.STRING:
@@ -235,26 +233,25 @@ class _Generator(object):
# Top level enums are in a namespace scope so the methods shouldn't be # Top level enums are in a namespace scope so the methods shouldn't be
# static. On the other hand, those declared inline (e.g. in an object) do. # static. On the other hand, those declared inline (e.g. in an object) do.
maybe_static = '' if is_toplevel else 'static ' maybe_static = '' if is_toplevel else 'static '
(c.Append() (c.Append() \
.Append('%sconst char* ToString(%s as_enum);' % .Append('%sconst char* ToString(%s as_enum);' %
(maybe_static, classname)) (maybe_static, classname)) \
.Append('%s%s Parse%s(std::string_view as_string);' % .Append('%s%s Parse%s(std::string_view as_string);' %
(maybe_static, classname, classname)) (maybe_static, classname, classname)) \
.Append( .Append(
'%sstd::u16string Get%sParseError(std::string_view as_string);' % '%sstd::u16string Get%sParseError(std::string_view as_string);' %
(maybe_static, classname)) (maybe_static, classname))
) )
elif type_.property_type in (PropertyType.CHOICES, elif type_.property_type in (PropertyType.CHOICES, PropertyType.OBJECT):
PropertyType.OBJECT):
if type_.description: if type_.description:
c.Comment(type_.description) c.Comment(type_.description)
(c.Sblock('struct %(classname)s {') (c.Sblock('struct %(classname)s {') \
.Append('%(classname)s();') .Append('%(classname)s();') \
.Append('~%(classname)s();') .Append('~%(classname)s();') \
.Append('%(classname)s(const %(classname)s&) = delete;') .Append('%(classname)s(const %(classname)s&) = delete;') \
.Append('%(classname)s& operator=(const %(classname)s&) = delete;') .Append('%(classname)s& operator=(const %(classname)s&) = delete;') \
.Append('%(classname)s(%(classname)s&& rhs) noexcept;') .Append('%(classname)s(%(classname)s&& rhs) noexcept;') \
.Append('%(classname)s& operator=(%(classname)s&& rhs) noexcept;') .Append('%(classname)s& operator=(%(classname)s&& rhs) noexcept;')
) )
@@ -263,28 +260,27 @@ class _Generator(object):
c.Comment('Manifest key constants.') c.Comment('Manifest key constants.')
c.Concat(self._GenerateManifestKeyConstants(type_.properties.values())) c.Concat(self._GenerateManifestKeyConstants(type_.properties.values()))
value_type = ('base::Value' value_type = ('base::Value' if type_.property_type is PropertyType.CHOICES
if type_.property_type is PropertyType.CHOICES else else 'base::Value::Dict')
'base::Value::Dict')
if (type_.origin.from_json or if (type_.origin.from_json
(type_.origin.from_manifest_keys and or (type_.origin.from_manifest_keys
type_.property_type is PropertyType.CHOICES)): and type_.property_type is PropertyType.CHOICES)):
(c.Append() (c.Append() \
.Comment('Populates a %s object from a base::Value& instance. Returns' .Comment('Populates a %s object from a base::Value& instance. Returns'
' whether |out| was successfully populated.' % classname) ' whether |out| was successfully populated.' % classname) \
.Append('static bool Populate(%s);' % self._GenerateParams( .Append('static bool Populate(%s);' % self._GenerateParams(
('const base::Value& value', '%s& out' % classname))) ('const base::Value& value', '%s& out' % classname)))
) )
if type_.property_type is not PropertyType.CHOICES: if type_.property_type is not PropertyType.CHOICES:
(c.Append() (c.Append() \
.Comment('Populates a %s object from a Dict& instance. Returns' .Comment('Populates a %s object from a Dict& instance. Returns'
' whether |out| was successfully populated.' % classname) ' whether |out| was successfully populated.' % classname) \
.Append('static bool Populate(%s);' % self._GenerateParams( .Append('static bool Populate(%s);' % self._GenerateParams(
('const base::Value::Dict& value', '%s& out' % classname))) ('const base::Value::Dict& value', '%s& out' % classname)))
) )
(c.Append() (c.Append() \
.Comment('Creates a deep copy of %s.' % classname) .Comment('Creates a deep copy of %s.' % classname) \
.Append('%s Clone() const;' % classname) .Append('%s Clone() const;' % classname)
) )
@@ -292,32 +288,32 @@ class _Generator(object):
classname, support_errors=self._generate_error_messages) classname, support_errors=self._generate_error_messages)
if type_.property_type is not PropertyType.CHOICES: if type_.property_type is not PropertyType.CHOICES:
(c.Append() (c.Append() \
.Comment('Creates a {classname} object from a base::Value::Dict,' .Comment('Creates a {classname} object from a base::Value::Dict,'
' or {failure} on failure.'.format( ' or {failure} on failure.'.format(
classname=classname, classname=classname,
failure=('unexpected' failure=('unexpected'
if self._generate_error_messages else 'nullopt'))) if self._generate_error_messages else 'nullopt'))) \
.Append('static {return_type} ' .Append('static {return_type} '
'FromValue(const base::Value::Dict& value);'.format( 'FromValue(const base::Value::Dict& value);'.format(
return_type=return_type)) return_type=return_type))
) )
(c.Append() (c.Append() \
.Comment('Creates a {classname} object from a base::Value,' .Comment('Creates a {classname} object from a base::Value,'
' or {failure} on failure.'.format( ' or {failure} on failure.'.format(
classname=classname, classname=classname,
failure=('unexpected' failure=('unexpected'
if self._generate_error_messages else 'nullopt'))) if self._generate_error_messages else 'nullopt'))) \
.Append('static {return_type} ' .Append('static {return_type} '
'FromValue(const base::Value& value);'.format( 'FromValue(const base::Value& value);'.format(
return_type=return_type)) return_type=return_type))
) )
if type_.origin.from_client: if type_.origin.from_client:
(c.Append() (c.Append() \
.Comment('Returns a new %s representing the serialized form of this' .Comment('Returns a new %s representing the serialized form of this'
'%s object.' % (value_type, classname)) '%s object.' % (value_type, classname)) \
.Append('%s ToValue() const;' % value_type) .Append('%s ToValue() const;' % value_type)
) )
@@ -330,13 +326,12 @@ class _Generator(object):
c.Cblock(self._GenerateTypes(type_.choices)) c.Cblock(self._GenerateTypes(type_.choices))
c.Append('// Choices:') c.Append('// Choices:')
for choice_type in type_.choices: for choice_type in type_.choices:
c.Append('%s as_%s;' % ( c.Append('%s as_%s;' % (self._type_helper.GetCppType(
self._type_helper.GetCppType(choice_type, is_optional=True), choice_type, is_optional=True), choice_type.unix_name))
choice_type.unix_name))
else: else:
properties = type_.properties.values() properties = type_.properties.values()
(c.Append() (c.Append() \
.Cblock(self._GenerateTypes(p.type_ for p in properties)) .Cblock(self._GenerateTypes(p.type_ for p in properties)) \
.Cblock(self._GenerateFields(properties))) .Cblock(self._GenerateFields(properties)))
if type_.additional_properties is not None: if type_.additional_properties is not None:
# Most additionalProperties actually have type "any", which is better # Most additionalProperties actually have type "any", which is better
@@ -344,7 +339,7 @@ class _Generator(object):
if type_.additional_properties.property_type == PropertyType.ANY: if type_.additional_properties.property_type == PropertyType.ANY:
c.Append('base::Value::Dict additional_properties;') c.Append('base::Value::Dict additional_properties;')
else: else:
(c.Cblock(self._GenerateType(type_.additional_properties)) (c.Cblock(self._GenerateType(type_.additional_properties)) \
.Append('std::map<std::string, %s> additional_properties;' % .Append('std::map<std::string, %s> additional_properties;' %
self._type_helper.GetCppType(type_.additional_properties)) self._type_helper.GetCppType(type_.additional_properties))
) )
@@ -357,10 +352,10 @@ class _Generator(object):
c = Code() c = Code()
# TODO(kalman): use event.unix_name not Classname. # TODO(kalman): use event.unix_name not Classname.
event_namespace = cpp_util.Classname(event.name) event_namespace = cpp_util.Classname(event.name)
(c.Append('namespace %s {' % event_namespace) (c.Append('namespace %s {' % event_namespace) \
.Append() .Append() \
.Concat(self._GenerateEventNameConstant(event)) .Concat(self._GenerateEventNameConstant(event)) \
.Concat(self._GenerateAsyncResponseArguments(event.params)) .Concat(self._GenerateAsyncResponseArguments(event.params)) \
.Append('} // namespace %s' % event_namespace) .Append('} // namespace %s' % event_namespace)
) )
return c return c
@@ -375,8 +370,8 @@ class _Generator(object):
# to not use the name. # to not use the name.
if function_namespace == 'SendMessage': if function_namespace == 'SendMessage':
function_namespace = 'PassMessage' function_namespace = 'PassMessage'
(c.Append('namespace %s {' % function_namespace) (c.Append('namespace %s {' % function_namespace) \
.Append() .Append() \
.Cblock(self._GenerateFunctionParams(function)) .Cblock(self._GenerateFunctionParams(function))
) )
if function.returns_async: if function.returns_async:
@@ -394,26 +389,26 @@ class _Generator(object):
(c.Sblock('struct Params {')) (c.Sblock('struct Params {'))
if self._generate_error_messages: if self._generate_error_messages:
(c.Append('static base::expected<Params, std::u16string> ' (c.Append('static base::expected<Params, std::u16string> '
'Create(const base::Value::List& args);') 'Create(const base::Value::List& args);') \
.Comment('DEPRECATED: prefer the variant of this function ' .Comment('DEPRECATED: prefer the variant of this function '
'returning errors with `base::expected`.') 'returning errors with `base::expected`.')
) )
(c.Append('static std::optional<Params> Create(%s);' % (c.Append('static std::optional<Params> Create(%s);' %
self._GenerateParams( self._GenerateParams(
('const base::Value::List& args',))) ('const base::Value::List& args',))) \
.Append('Params(const Params&) = delete;') .Append('Params(const Params&) = delete;') \
.Append('Params& operator=(const Params&) = delete;') .Append('Params& operator=(const Params&) = delete;') \
.Append('Params(Params&& rhs) noexcept;') .Append('Params(Params&& rhs) noexcept;') \
.Append('Params& operator=(Params&& rhs) noexcept;') .Append('Params& operator=(Params&& rhs) noexcept;') \
.Append('~Params();') .Append('~Params();') \
.Append() .Append() \
.Cblock(self._GenerateTypes(p.type_ for p in function.params)) .Cblock(self._GenerateTypes(p.type_ for p in function.params)) \
.Cblock(self._GenerateFields(function.params)) .Cblock(self._GenerateFields(function.params)) \
.Eblock() .Eblock() \
.Append() .Append() \
.Sblock(' private:') .Sblock(' private:') \
.Append('Params();') .Append('Params();') \
.Eblock('};') .Eblock('};')
) )
return c return c
@@ -424,9 +419,10 @@ class _Generator(object):
""" """
c = Code() c = Code()
for type_ in types: for type_ in types:
c.Cblock(self._GenerateType(type_, c.Cblock(
is_toplevel=is_toplevel, self._GenerateType(type_,
generate_typedefs=generate_typedefs)) is_toplevel=is_toplevel,
generate_typedefs=generate_typedefs))
return c return c
def _GenerateManifestKeys(self): def _GenerateManifestKeys(self):
@@ -446,26 +442,23 @@ class _Generator(object):
# manifest types. # manifest types.
if type_.IsRootManifestKeyType(): if type_.IsRootManifestKeyType():
params = [ params = [
'const base::Value::Dict& root_dict', 'const base::Value::Dict& root_dict',
'%s& out' % classname, '%s& out' % classname, 'std::u16string& error'
'std::u16string& error'
] ]
comment = ( comment = (
'Parses manifest keys for this namespace. Any keys not available to the' 'Parses manifest keys for this namespace. Any keys not available to'
' manifest will be ignored. On a parsing error, false is returned and ' ' the manifest will be ignored. On a parsing error, false is returned'
'|error| is populated.') ' and |error| is populated.')
else: else:
params = [ params = [
'const base::Value::Dict& root_dict', 'const base::Value::Dict& root_dict', 'std::string_view key',
'std::string_view key', '%s& out' % classname, 'std::u16string& error',
'%s& out' % classname, 'std::vector<std::string_view>& error_path_reversed'
'std::u16string& error',
'std::vector<std::string_view>& error_path_reversed'
] ]
comment = ( comment = (
'Parses the given |key| from |root_dict|. Any keys not available to the' 'Parses the given |key| from |root_dict|. Any keys not available to'
' manifest will be ignored. On a parsing error, false is returned and ' ' the manifest will be ignored. On a parsing error, false is returned'
'|error| and |error_path_reversed| are populated.') ' and |error| and |error_path_reversed| are populated.')
c = Code() c = Code()
c.Append().Comment(comment) c.Append().Comment(comment)
@@ -499,18 +492,18 @@ class _Generator(object):
for param in params: for param in params:
if param.description: if param.description:
c.Comment(param.description) c.Comment(param.description)
declaration_list.append(cpp_util.GetParameterDeclaration( declaration_list.append(
param, self._type_helper.GetCppType(param.type_))) cpp_util.GetParameterDeclaration(
c.Append('base::Value::List Create(%s);' % param, self._type_helper.GetCppType(param.type_)))
', '.join(declaration_list)) c.Append('base::Value::List Create(%s);' % ', '.join(declaration_list))
return c return c
def _GenerateEventNameConstant(self, event): def _GenerateEventNameConstant(self, event):
"""Generates a constant string array for the event name. """Generates a constant string array for the event name.
""" """
c = Code() c = Code()
c.Append('extern const char kEventName[]; // "%s.%s"' % ( c.Append('extern const char kEventName[]; // "%s.%s"' %
self._namespace.name, event.name)) (self._namespace.name, event.name))
c.Append() c.Append()
return c return c
@@ -518,15 +511,14 @@ class _Generator(object):
"""Generates namespace for passing a function's result back. """Generates namespace for passing a function's result back.
""" """
c = Code() c = Code()
(c.Append('namespace Results {') (c.Append('namespace Results {') \
.Append() .Append() \
.Concat(self._GenerateAsyncResponseArguments(returns_async.params)) .Concat(self._GenerateAsyncResponseArguments(returns_async.params)) \
.Append('} // namespace Results') .Append('} // namespace Results')
) )
return c return c
def _GenerateParams( def _GenerateParams(self, params, generate_error_messages=None):
self, params, generate_error_messages=None):
"""Builds the parameter list for a function, given an array of parameters. """Builds the parameter list for a function, given an array of parameters.
If |generate_error_messages| is specified, it overrides If |generate_error_messages| is specified, it overrides
|self._generate_error_messages|. |self._generate_error_messages|.
@@ -539,5 +531,5 @@ class _Generator(object):
if generate_error_messages is None: if generate_error_messages is None:
generate_error_messages = self._generate_error_messages generate_error_messages = self._generate_error_messages
if generate_error_messages: if generate_error_messages:
params += ('std::u16string& error',) params += ('std::u16string& error', )
return ', '.join(str(p) for p in params) return ', '.join(str(p) for p in params)

@@ -32,6 +32,7 @@ else:
finally: finally:
sys.path.pop(0) sys.path.pop(0)
def ProcessComment(comment): def ProcessComment(comment):
''' '''
Convert a comment into a parent comment and a list of parameter comments. Convert a comment into a parent comment and a list of parameter comments.
@@ -57,6 +58,7 @@ def ProcessComment(comment):
} }
) )
''' '''
def add_paragraphs(content): def add_paragraphs(content):
paragraphs = content.split('\n\n') paragraphs = content.split('\n\n')
if len(paragraphs) < 2: if len(paragraphs) < 2:
@@ -69,8 +71,8 @@ def ProcessComment(comment):
# Get the parent comment (everything before the first parameter comment. # Get the parent comment (everything before the first parameter comment.
first_parameter_location = (parameter_starts[0].start() first_parameter_location = (parameter_starts[0].start()
if parameter_starts else len(comment)) if parameter_starts else len(comment))
parent_comment = (add_paragraphs(comment[:first_parameter_location].strip()) parent_comment = (add_paragraphs(
.replace('\n', '')) comment[:first_parameter_location].strip()).replace('\n', ''))
params = OrderedDict() params = OrderedDict()
for (cur_param, next_param) in itertools.zip_longest(parameter_starts, for (cur_param, next_param) in itertools.zip_longest(parameter_starts,
@@ -81,9 +83,9 @@ def ProcessComment(comment):
# beginning of the next parameter's introduction. # beginning of the next parameter's introduction.
param_comment_start = cur_param.end() param_comment_start = cur_param.end()
param_comment_end = next_param.start() if next_param else len(comment) param_comment_end = next_param.start() if next_param else len(comment)
params[param_name] = ( params[param_name] = (add_paragraphs(
add_paragraphs(comment[param_comment_start:param_comment_end].strip()) comment[param_comment_start:param_comment_end].strip()).replace(
.replace('\n', '')) '\n', ''))
return (parent_comment, params) return (parent_comment, params)
@@ -94,6 +96,7 @@ class Callspec(object):
a tuple: a tuple:
(name, list of function parameters, return type, async return) (name, list of function parameters, return type, async return)
''' '''
def __init__(self, callspec_node, comment): def __init__(self, callspec_node, comment):
self.node = callspec_node self.node = callspec_node
self.comment = comment self.comment = comment
@@ -103,9 +106,10 @@ class Callspec(object):
return_type = None return_type = None
returns_async = None returns_async = None
if self.node.GetProperty('TYPEREF') not in ('void', None): if self.node.GetProperty('TYPEREF') not in ('void', None):
return_type = Typeref(self.node.GetProperty('TYPEREF'), return_type = Typeref(self.node.GetProperty('TYPEREF'), self.node.parent,
self.node.parent, {
{'name': self.node.GetName()}).process(callbacks) 'name': self.node.GetName()
}).process(callbacks)
# The IDL parser doesn't allow specifying return types as optional. # The IDL parser doesn't allow specifying return types as optional.
# Instead we infer any object return values to be optional. # Instead we infer any object return values to be optional.
# TODO(asargent): fix the IDL parser to support optional return types. # TODO(asargent): fix the IDL parser to support optional return types.
@@ -127,32 +131,25 @@ class Callspec(object):
# trailingCallbackIsFunctionParameter extended attribute). # trailingCallbackIsFunctionParameter extended attribute).
# TODO(tjudkins): Once IDL definitions are changed to describe returning # TODO(tjudkins): Once IDL definitions are changed to describe returning
# promises, we can condition on that instead. # promises, we can condition on that instead.
if ( if (use_returns_async
use_returns_async
and not self.node.GetProperty('trailingCallbackIsFunctionParameter') and not self.node.GetProperty('trailingCallbackIsFunctionParameter')
and len(parameters) > 0 and len(parameters) > 0 and parameters[-1].get('type') == 'function'):
and parameters[-1].get('type') == 'function'
):
returns_async = parameters.pop() returns_async = parameters.pop()
# The returns_async field is inherently a function, so doesn't need type # The returns_async field is inherently a function, so doesn't need type
# specified on it. # specified on it.
returns_async.pop('type') returns_async.pop('type')
does_not_support_promises = self.node.GetProperty( does_not_support_promises = self.node.GetProperty(
'doesNotSupportPromises' 'doesNotSupportPromises')
)
if does_not_support_promises is not None: if does_not_support_promises is not None:
returns_async['does_not_support_promises'] = does_not_support_promises returns_async['does_not_support_promises'] = does_not_support_promises
else: else:
assert return_type is None, ( assert return_type is None, (
'Function "%s" cannot support promises and also have a ' 'Function "%s" cannot support promises and also have a '
'return value.' % self.node.GetName() 'return value.' % self.node.GetName())
)
else: else:
assert not self.node.GetProperty('doesNotSupportPromises'), ( assert not self.node.GetProperty('doesNotSupportPromises'), (
'Callspec "%s" does not need to specify [doesNotSupportPromises] if ' 'Callspec "%s" does not need to specify [doesNotSupportPromises] if '
'it does not have a trailing callback' 'it does not have a trailing callback' % self.node.GetName())
% self.node.GetName()
)
return (self.node.GetName(), parameters, return_type, returns_async) return (self.node.GetName(), parameters, return_type, returns_async)
@@ -162,13 +159,14 @@ class Param(object):
Given a Param node representing a function parameter, converts into a Python Given a Param node representing a function parameter, converts into a Python
dictionary that the JSON schema compiler expects to see. dictionary that the JSON schema compiler expects to see.
''' '''
def __init__(self, param_node): def __init__(self, param_node):
self.node = param_node self.node = param_node
def process(self, callbacks): def process(self, callbacks):
return Typeref(self.node.GetProperty('TYPEREF'), return Typeref(self.node.GetProperty('TYPEREF'), self.node, {
self.node, 'name': self.node.GetName()
{'name': self.node.GetName()}).process(callbacks) }).process(callbacks)
class Dictionary(object): class Dictionary(object):
@@ -176,6 +174,7 @@ class Dictionary(object):
Given an IDL Dictionary node, converts into a Python dictionary that the JSON Given an IDL Dictionary node, converts into a Python dictionary that the JSON
schema compiler expects to see. schema compiler expects to see.
''' '''
def __init__(self, dictionary_node): def __init__(self, dictionary_node):
self.node = dictionary_node self.node = dictionary_node
@@ -185,9 +184,11 @@ class Dictionary(object):
if node.cls == 'Member': if node.cls == 'Member':
k, v = Member(node).process(callbacks) k, v = Member(node).process(callbacks)
properties[k] = v properties[k] = v
result = {'id': self.node.GetName(), result = {
'properties': properties, 'id': self.node.GetName(),
'type': 'object'} 'properties': properties,
'type': 'object'
}
# If this has the `ignoreAdditionalProperties` extended attribute, copy it # If this has the `ignoreAdditionalProperties` extended attribute, copy it
# into the resulting object with a value of True. # into the resulting object with a value of True.
if self.node.GetProperty('ignoreAdditionalProperties'): if self.node.GetProperty('ignoreAdditionalProperties'):
@@ -208,19 +209,22 @@ class Member(object):
where the value is a Python dictionary that the JSON schema compiler expects where the value is a Python dictionary that the JSON schema compiler expects
to see. to see.
''' '''
def __init__(self, member_node): def __init__(self, member_node):
self.node = member_node self.node = member_node
def process( def process(self,
self, callbacks, functions_are_properties=False, use_returns_async=False callbacks,
): functions_are_properties=False,
use_returns_async=False):
properties = OrderedDict() properties = OrderedDict()
name = self.node.GetName() name = self.node.GetName()
if self.node.GetProperty('deprecated'): if self.node.GetProperty('deprecated'):
properties['deprecated'] = self.node.GetProperty('deprecated') properties['deprecated'] = self.node.GetProperty('deprecated')
for property_name in ['nodoc', 'nocompile', 'nodart', for property_name in [
'serializableFunction']: 'nodoc', 'nocompile', 'nodart', 'serializableFunction'
]:
if self.node.GetProperty(property_name): if self.node.GetProperty(property_name):
properties[property_name] = True properties[property_name] = True
@@ -230,27 +234,24 @@ class Member(object):
if self.node.GetProperty('platforms'): if self.node.GetProperty('platforms'):
properties['platforms'] = list(self.node.GetProperty('platforms')) properties['platforms'] = list(self.node.GetProperty('platforms'))
for option_name, sanitizer in [ for option_name, sanitizer in [('maxListeners', int),
('maxListeners', int), ('supportsFilters', lambda s: s == 'true'),
('supportsFilters', lambda s: s == 'true'), ('supportsListeners', lambda s: s == 'true'),
('supportsListeners', lambda s: s == 'true'), ('supportsRules', lambda s: s == 'true')]:
('supportsRules', lambda s: s == 'true')]:
if self.node.GetProperty(option_name): if self.node.GetProperty(option_name):
if 'options' not in properties: if 'options' not in properties:
properties['options'] = {} properties['options'] = {}
properties['options'][option_name] = sanitizer(self.node.GetProperty( properties['options'][option_name] = sanitizer(
option_name)) self.node.GetProperty(option_name))
type_override = None type_override = None
parameter_comments = OrderedDict() parameter_comments = OrderedDict()
for node in self.node.GetChildren(): for node in self.node.GetChildren():
if node.cls == 'Comment': if node.cls == 'Comment':
(parent_comment, parameter_comments) = ProcessComment( (parent_comment, parameter_comments) = ProcessComment(node.GetName())
node.GetName())
properties['description'] = parent_comment properties['description'] = parent_comment
elif node.cls == 'Callspec': elif node.cls == 'Callspec':
name, parameters, return_type, returns_async = Callspec( name, parameters, return_type, returns_async = Callspec(
node, parameter_comments node, parameter_comments).process(use_returns_async, callbacks)
).process(use_returns_async, callbacks)
if functions_are_properties: if functions_are_properties:
# If functions are treated as properties (which will happen if the # If functions are treated as properties (which will happen if the
# interface is named Properties) then this isn't a function, it's a # interface is named Properties) then this isn't a function, it's a
@@ -258,14 +259,14 @@ class Member(object):
# property type is the return type. This is an egregious hack in lieu # property type is the return type. This is an egregious hack in lieu
# of the IDL parser supporting 'const'. # of the IDL parser supporting 'const'.
assert parameters == [], ( assert parameters == [], (
'Property "%s" must be no-argument functions ' 'Property "%s" must be no-argument functions '
'with a non-void return type' % name) 'with a non-void return type' % name)
assert return_type is not None, ( assert return_type is not None, (
'Property "%s" must be no-argument functions ' 'Property "%s" must be no-argument functions '
'with a non-void return type' % name) 'with a non-void return type' % name)
assert 'type' in return_type, ( assert 'type' in return_type, (
'Property return type "%s" from "%s" must specify a ' 'Property return type "%s" from "%s" must specify a '
'fundamental IDL type.' % (pprint.pformat(return_type), name)) 'fundamental IDL type.' % (pprint.pformat(return_type), name))
type_override = return_type['type'] type_override = return_type['type']
else: else:
type_override = 'function' type_override = 'function'
@@ -279,8 +280,8 @@ class Member(object):
if type_override is not None: if type_override is not None:
properties['type'] = type_override properties['type'] = type_override
else: else:
properties = Typeref(self.node.GetProperty('TYPEREF'), properties = Typeref(self.node.GetProperty('TYPEREF'), self.node,
self.node, properties).process(callbacks) properties).process(callbacks)
value = self.node.GetProperty('value') value = self.node.GetProperty('value')
if value is not None: if value is not None:
# IDL always returns values as strings, so cast to their real type. # IDL always returns values as strings, so cast to their real type.
@@ -298,9 +299,9 @@ class Member(object):
return float(string_value) return float(string_value)
# Add more as necessary. # Add more as necessary.
assert json_type == 'string', ( assert json_type == 'string', (
'No rule exists to cast JSON Schema type "%s" to its equivalent ' 'No rule exists to cast JSON Schema type "%s" to its equivalent '
'Python type for value "%s". You must add a new rule here.' % 'Python type for value "%s". You must add a new rule here.' %
(json_type, string_value)) (json_type, string_value))
return string_value return string_value
@@ -310,6 +311,7 @@ class Typeref(object):
function parameter, converts into a Python dictionary that the JSON schema function parameter, converts into a Python dictionary that the JSON schema
compiler expects to see. compiler expects to see.
''' '''
def __init__(self, typeref, parent, additional_properties): def __init__(self, typeref, parent, additional_properties):
self.typeref = typeref self.typeref = typeref
self.parent = parent self.parent = parent
@@ -363,11 +365,11 @@ class Typeref(object):
properties['additionalProperties'] = OrderedDict() properties['additionalProperties'] = OrderedDict()
properties['additionalProperties']['type'] = 'any' properties['additionalProperties']['type'] = 'any'
elif self.parent.GetPropertyLocal('Union'): elif self.parent.GetPropertyLocal('Union'):
properties['choices'] = [Typeref(node.GetProperty('TYPEREF'), properties['choices'] = [
node, Typeref(node.GetProperty('TYPEREF'), node,
OrderedDict()).process(callbacks) OrderedDict()).process(callbacks)
for node in self.parent.GetChildren() for node in self.parent.GetChildren() if node.cls == 'Option'
if node.cls == 'Option'] ]
elif self.typeref is None: elif self.typeref is None:
properties['type'] = 'function' properties['type'] = 'function'
else: else:
@@ -390,6 +392,7 @@ class Enum(object):
Given an IDL Enum node, converts into a Python dictionary that the JSON Given an IDL Enum node, converts into a Python dictionary that the JSON
schema compiler expects to see. schema compiler expects to see.
''' '''
def __init__(self, enum_node): def __init__(self, enum_node):
self.node = enum_node self.node = enum_node
self.description = '' self.description = ''
@@ -411,12 +414,15 @@ class Enum(object):
self.description = ProcessComment(node.GetName())[0] self.description = ProcessComment(node.GetName())[0]
else: else:
sys.exit('Did not process %s %s' % (node.cls, node)) sys.exit('Did not process %s %s' % (node.cls, node))
result = {'id' : self.node.GetName(), result = {
'description': self.description, 'id': self.node.GetName(),
'type': 'string', 'description': self.description,
'enum': enum} 'type': 'string',
for property_name in ['cpp_enum_prefix_override', 'inline_doc', 'enum': enum
'noinline_doc', 'nodoc']: }
for property_name in [
'cpp_enum_prefix_override', 'inline_doc', 'noinline_doc', 'nodoc'
]:
if self.node.GetProperty(property_name): if self.node.GetProperty(property_name):
result[property_name] = self.node.GetProperty(property_name) result[property_name] = self.node.GetProperty(property_name)
if self.node.GetProperty('deprecated'): if self.node.GetProperty('deprecated'):
@@ -485,19 +491,19 @@ class Namespace(object):
compiler_options = self.compiler_options or {} compiler_options = self.compiler_options or {}
documentation_options = self.documentation_options or {} documentation_options = self.documentation_options or {}
return { return {
'namespace': self.namespace.GetName(), 'namespace': self.namespace.GetName(),
'description': self.description, 'description': self.description,
'nodoc': self.nodoc, 'nodoc': self.nodoc,
'types': self.types, 'types': self.types,
'functions': self.functions, 'functions': self.functions,
'properties': self.properties, 'properties': self.properties,
'manifest_keys': self.manifest_keys, 'manifest_keys': self.manifest_keys,
'internal': self.internal, 'internal': self.internal,
'events': self.events, 'events': self.events,
'platforms': self.platforms, 'platforms': self.platforms,
'compiler_options': compiler_options, 'compiler_options': compiler_options,
'deprecated': self.deprecated, 'deprecated': self.deprecated,
'documentation_options': documentation_options 'documentation_options': documentation_options
} }
def process_interface(self, node, functions_are_properties=False): def process_interface(self, node, functions_are_properties=False):
@@ -542,9 +548,12 @@ class IDLSchema(object):
if not description: if not description:
# TODO(kalman): Go back to throwing an error here. # TODO(kalman): Go back to throwing an error here.
print('%s must have a namespace-level comment. This will ' print('%s must have a namespace-level comment. This will '
'appear on the API summary page.' % node.GetName()) 'appear on the API summary page.' % node.GetName())
description = '' description = ''
namespace = Namespace(node, description, nodoc, internal, namespace = Namespace(node,
description,
nodoc,
internal,
platforms=platforms, platforms=platforms,
compiler_options=compiler_options or None, compiler_options=compiler_options or None,
deprecated=deprecated, deprecated=deprecated,
@@ -621,8 +630,9 @@ def Main():
contents = sys.stdin.read() contents = sys.stdin.read()
for i, char in enumerate(contents): for i, char in enumerate(contents):
if not char.isascii(): if not char.isascii():
raise Exception('Non-ascii character "%s" (ord %d) found at offset %d.' raise Exception(
% (char, ord(char), i)) 'Non-ascii character "%s" (ord %d) found at offset %d.' %
(char, ord(char), i))
idl = idl_parser.IDLParser().ParseData(contents, '<stdin>') idl = idl_parser.IDLParser().ParseData(contents, '<stdin>')
schema = IDLSchema(idl).process() schema = IDLSchema(idl).process()
print(json.dumps(schema, indent=2)) print(json.dumps(schema, indent=2))

@@ -8,6 +8,7 @@ import unittest
from json_parse import OrderedDict from json_parse import OrderedDict
def getFunction(schema, name): def getFunction(schema, name):
for item in schema['functions']: for item in schema['functions']:
if item['name'] == name: if item['name'] == name:
@@ -19,10 +20,12 @@ def getParams(schema, name):
function = getFunction(schema, name) function = getFunction(schema, name)
return function['parameters'] return function['parameters']
def getReturnsAsync(schema, name): def getReturnsAsync(schema, name):
function = getFunction(schema, name) function = getFunction(schema, name)
return function.get('returns_async', False) return function.get('returns_async', False)
def getReturns(schema, name): def getReturns(schema, name):
function = getFunction(schema, name) function = getFunction(schema, name)
return function['returns'] return function['returns']
@@ -35,6 +38,7 @@ def getType(schema, id):
class IdlSchemaTest(unittest.TestCase): class IdlSchemaTest(unittest.TestCase):
def setUp(self): def setUp(self):
loaded = idl_schema.Load('test/idl_basics.idl') loaded = idl_schema.Load('test/idl_basics.idl')
self.assertEqual(1, len(loaded)) self.assertEqual(1, len(loaded))
@@ -44,66 +48,123 @@ class IdlSchemaTest(unittest.TestCase):
def testSimpleCallbacks(self): def testSimpleCallbacks(self):
schema = self.idl_basics schema = self.idl_basics
expected = {'name': 'cb', 'parameters':[]} expected = {'name': 'cb', 'parameters': []}
self.assertEqual(expected, getReturnsAsync(schema, 'function4')) self.assertEqual(expected, getReturnsAsync(schema, 'function4'))
expected = {'name': 'cb', expected = {'name': 'cb', 'parameters': [{'name': 'x', 'type': 'integer'}]}
'parameters':[{'name': 'x', 'type': 'integer'}]}
self.assertEqual(expected, getReturnsAsync(schema, 'function5')) self.assertEqual(expected, getReturnsAsync(schema, 'function5'))
expected = {'name': 'cb', expected = {
'parameters':[{'name': 'arg', '$ref': 'MyType1'}]} 'name': 'cb',
'parameters': [{
'name': 'arg',
'$ref': 'MyType1'
}]
}
self.assertEqual(expected, getReturnsAsync(schema, 'function6')) self.assertEqual(expected, getReturnsAsync(schema, 'function6'))
def testCallbackWithArrayArgument(self): def testCallbackWithArrayArgument(self):
schema = self.idl_basics schema = self.idl_basics
expected = {'name': 'cb', expected = {
'parameters':[{'name': 'arg', 'type': 'array', 'name':
'items':{'$ref': 'MyType2'}}]} 'cb',
'parameters': [{
'name': 'arg',
'type': 'array',
'items': {
'$ref': 'MyType2'
}
}]
}
self.assertEqual(expected, getReturnsAsync(schema, 'function12')) self.assertEqual(expected, getReturnsAsync(schema, 'function12'))
def testArrayOfCallbacks(self): def testArrayOfCallbacks(self):
schema = idl_schema.Load('test/idl_function_types.idl')[0] schema = idl_schema.Load('test/idl_function_types.idl')[0]
expected = [{'type': 'array', 'name': 'callbacks', expected = [{
'items':{'type': 'function', 'name': 'MyCallback', 'type': 'array',
'parameters':[{'type': 'integer', 'name': 'x'}]}}] 'name': 'callbacks',
'items': {
'type': 'function',
'name': 'MyCallback',
'parameters': [{
'type': 'integer',
'name': 'x'
}]
}
}]
self.assertEqual(expected, getParams(schema, 'whatever')) self.assertEqual(expected, getParams(schema, 'whatever'))
def testProperties(self): def testProperties(self):
self.assertEqual({ self.assertEqual(
'x': {'name': 'x', 'type': 'integer', {
'description': 'This comment tests "double-quotes".'}, 'x': {
'y': {'name': 'y', 'type': 'string'}, 'name': 'x',
'z': {'name': 'z', 'type': 'string'}, 'type': 'integer',
'a': {'name': 'a', 'type': 'string'}, 'description': 'This comment tests "double-quotes".'
'b': {'name': 'b', 'type': 'string'}, },
'c': {'name': 'c', 'type': 'string'}}, 'y': {
getType(self.idl_basics, 'MyType1')['properties']) 'name': 'y',
'type': 'string'
},
'z': {
'name': 'z',
'type': 'string'
},
'a': {
'name': 'a',
'type': 'string'
},
'b': {
'name': 'b',
'type': 'string'
},
'c': {
'name': 'c',
'type': 'string'
}
},
getType(self.idl_basics, 'MyType1')['properties'])
def testMemberOrdering(self): def testMemberOrdering(self):
self.assertEqual( self.assertEqual(['x', 'y', 'z', 'a', 'b', 'c'],
['x', 'y', 'z', 'a', 'b', 'c'], list(
list(getType(self.idl_basics, 'MyType1')['properties'].keys())) getType(self.idl_basics,
'MyType1')['properties'].keys()))
def testEnum(self): def testEnum(self):
schema = self.idl_basics schema = self.idl_basics
expected = {'enum': [{'name': 'name1', 'description': 'comment1'}, expected = {
{'name': 'name2'}], 'enum': [{
'description': 'Enum description', 'name': 'name1',
'type': 'string', 'id': 'EnumType'} 'description': 'comment1'
}, {
'name': 'name2'
}],
'description': 'Enum description',
'type': 'string',
'id': 'EnumType'
}
self.assertEqual(expected, getType(schema, expected['id'])) self.assertEqual(expected, getType(schema, expected['id']))
expected_params = [{'name': 'type', '$ref': 'EnumType'}] expected_params = [{'name': 'type', '$ref': 'EnumType'}]
expected_returns_async = { expected_returns_async = {
'name': 'cb', 'name': 'cb',
'parameters':[{'name': 'type', '$ref': 'EnumType'}]} 'parameters': [{
'name': 'type',
'$ref': 'EnumType'
}]
}
self.assertEqual(expected_params, getParams(schema, 'function13')) self.assertEqual(expected_params, getParams(schema, 'function13'))
self.assertEqual( self.assertEqual(expected_returns_async,
expected_returns_async, getReturnsAsync(schema, 'function13') getReturnsAsync(schema, 'function13'))
)
expected = [{'items': {'$ref': 'EnumType'}, 'name': 'types', expected = [{
'type': 'array'}] 'items': {
'$ref': 'EnumType'
},
'name': 'types',
'type': 'array'
}]
self.assertEqual(expected, getParams(schema, 'function14')) self.assertEqual(expected, getParams(schema, 'function14'))
def testScopedArguments(self): def testScopedArguments(self):
@@ -111,19 +172,29 @@ class IdlSchemaTest(unittest.TestCase):
expected = [{'name': 'value', '$ref': 'idl_other_namespace.SomeType'}] expected = [{'name': 'value', '$ref': 'idl_other_namespace.SomeType'}]
self.assertEqual(expected, getParams(schema, 'function20')) self.assertEqual(expected, getParams(schema, 'function20'))
expected = [{'items': {'$ref': 'idl_other_namespace.SomeType'}, expected = [{
'name': 'values', 'items': {
'type': 'array'}] '$ref': 'idl_other_namespace.SomeType'
},
'name': 'values',
'type': 'array'
}]
self.assertEqual(expected, getParams(schema, 'function21')) self.assertEqual(expected, getParams(schema, 'function21'))
expected = [{'name': 'value', expected = [{
'$ref': 'idl_other_namespace.sub_namespace.AnotherType'}] 'name': 'value',
'$ref': 'idl_other_namespace.sub_namespace.AnotherType'
}]
self.assertEqual(expected, getParams(schema, 'function22')) self.assertEqual(expected, getParams(schema, 'function22'))
expected = [{'items': {'$ref': 'idl_other_namespace.sub_namespace.' expected = [{
'AnotherType'}, 'items': {
'name': 'values', '$ref': 'idl_other_namespace.sub_namespace.'
'type': 'array'}] 'AnotherType'
},
'name': 'values',
'type': 'array'
}]
self.assertEqual(expected, getParams(schema, 'function23')) self.assertEqual(expected, getParams(schema, 'function23'))
def testNoCompile(self): def testNoCompile(self):
@@ -151,45 +222,73 @@ class IdlSchemaTest(unittest.TestCase):
'name': 'name3', 'name': 'name3',
'description': 'comment3' 'description': 'comment3'
}], }],
'type': 'string', 'type':
'id': 'EnumTypeWithNoDocValue', 'string',
'description': '' 'id':
'EnumTypeWithNoDocValue',
'description':
''
} }
self.assertEqual(expected, getType(schema, expected['id'])) self.assertEqual(expected, getType(schema, expected['id']))
def testInternalNamespace(self): def testInternalNamespace(self):
idl_basics = self.idl_basics idl_basics = self.idl_basics
self.assertEqual('idl_basics', idl_basics['namespace']) self.assertEqual('idl_basics', idl_basics['namespace'])
self.assertTrue(idl_basics['internal']) self.assertTrue(idl_basics['internal'])
self.assertFalse(idl_basics['nodoc']) self.assertFalse(idl_basics['nodoc'])
def testReturnTypes(self): def testReturnTypes(self):
schema = self.idl_basics schema = self.idl_basics
self.assertEqual({'name': 'function24', 'type': 'integer'}, self.assertEqual({
getReturns(schema, 'function24')) 'name': 'function24',
self.assertEqual({'name': 'function25', '$ref': 'MyType1', 'type': 'integer'
'optional': True}, }, getReturns(schema, 'function24'))
getReturns(schema, 'function25')) self.assertEqual({
self.assertEqual({'name': 'function26', 'type': 'array', 'name': 'function25',
'items': {'$ref': 'MyType1'}}, '$ref': 'MyType1',
getReturns(schema, 'function26')) 'optional': True
self.assertEqual({'name': 'function27', '$ref': 'EnumType', }, getReturns(schema, 'function25'))
'optional': True}, self.assertEqual(
getReturns(schema, 'function27')) {
self.assertEqual({'name': 'function28', 'type': 'array', 'name': 'function26',
'items': {'$ref': 'EnumType'}}, 'type': 'array',
getReturns(schema, 'function28')) 'items': {
self.assertEqual({'name': 'function29', '$ref': '$ref': 'MyType1'
'idl_other_namespace.SomeType', }
'optional': True}, }, getReturns(schema, 'function26'))
getReturns(schema, 'function29')) self.assertEqual(
self.assertEqual({'name': 'function30', 'type': 'array', {
'items': {'$ref': 'idl_other_namespace.SomeType'}}, 'name': 'function27',
getReturns(schema, 'function30')) '$ref': 'EnumType',
'optional': True
}, getReturns(schema, 'function27'))
self.assertEqual(
{
'name': 'function28',
'type': 'array',
'items': {
'$ref': 'EnumType'
}
}, getReturns(schema, 'function28'))
self.assertEqual(
{
'name': 'function29',
'$ref': 'idl_other_namespace.SomeType',
'optional': True
}, getReturns(schema, 'function29'))
self.assertEqual(
{
'name': 'function30',
'type': 'array',
'items': {
'$ref': 'idl_other_namespace.SomeType'
}
}, getReturns(schema, 'function30'))
def testIgnoresAdditionalPropertiesOnType(self): def testIgnoresAdditionalPropertiesOnType(self):
self.assertTrue(getType(self.idl_basics, 'IgnoreAdditionalPropertiesType') self.assertTrue(
['ignoreAdditionalProperties']) getType(self.idl_basics,
'IgnoreAdditionalPropertiesType')['ignoreAdditionalProperties'])
def testChromeOSPlatformsNamespace(self): def testChromeOSPlatformsNamespace(self):
schema = idl_schema.Load('test/idl_namespace_chromeos.idl')[0] schema = idl_schema.Load('test/idl_namespace_chromeos.idl')[0]
@@ -206,7 +305,7 @@ class IdlSchemaTest(unittest.TestCase):
def testNonSpecificPlatformsNamespace(self): def testNonSpecificPlatformsNamespace(self):
schema = idl_schema.Load('test/idl_namespace_non_specific_platforms.idl')[0] schema = idl_schema.Load('test/idl_namespace_non_specific_platforms.idl')[0]
self.assertEqual('idl_namespace_non_specific_platforms', self.assertEqual('idl_namespace_non_specific_platforms',
schema['namespace']) schema['namespace'])
expected = None expected = None
self.assertEqual(expected, schema['platforms']) self.assertEqual(expected, schema['platforms'])
@@ -214,17 +313,16 @@ class IdlSchemaTest(unittest.TestCase):
schema = idl_schema.Load('test/idl_generate_error_messages.idl')[0] schema = idl_schema.Load('test/idl_generate_error_messages.idl')[0]
self.assertEqual('idl_generate_error_messages', schema['namespace']) self.assertEqual('idl_generate_error_messages', schema['namespace'])
self.assertTrue(schema['compiler_options'].get('generate_error_messages', self.assertTrue(schema['compiler_options'].get('generate_error_messages',
False)) False))
schema = idl_schema.Load('test/idl_basics.idl')[0] schema = idl_schema.Load('test/idl_basics.idl')[0]
self.assertEqual('idl_basics', schema['namespace']) self.assertEqual('idl_basics', schema['namespace'])
self.assertFalse(schema['compiler_options'].get('generate_error_messages', self.assertFalse(schema['compiler_options'].get('generate_error_messages',
False)) False))
def testSpecificImplementNamespace(self): def testSpecificImplementNamespace(self):
schema = idl_schema.Load('test/idl_namespace_specific_implement.idl')[0] schema = idl_schema.Load('test/idl_namespace_specific_implement.idl')[0]
self.assertEqual('idl_namespace_specific_implement', self.assertEqual('idl_namespace_specific_implement', schema['namespace'])
schema['namespace'])
expected = 'idl_namespace_specific_implement.idl' expected = 'idl_namespace_specific_implement.idl'
self.assertEqual(expected, schema['compiler_options']['implemented_in']) self.assertEqual(expected, schema['compiler_options']['implemented_in'])
@@ -232,39 +330,39 @@ class IdlSchemaTest(unittest.TestCase):
schema = idl_schema.Load( schema = idl_schema.Load(
'test/idl_namespace_specific_implement_chromeos.idl')[0] 'test/idl_namespace_specific_implement_chromeos.idl')[0]
self.assertEqual('idl_namespace_specific_implement_chromeos', self.assertEqual('idl_namespace_specific_implement_chromeos',
schema['namespace']) schema['namespace'])
expected_implemented_path = 'idl_namespace_specific_implement_chromeos.idl' expected_implemented_path = 'idl_namespace_specific_implement_chromeos.idl'
expected_platform = ['chromeos'] expected_platform = ['chromeos']
self.assertEqual(expected_implemented_path, self.assertEqual(expected_implemented_path,
schema['compiler_options']['implemented_in']) schema['compiler_options']['implemented_in'])
self.assertEqual(expected_platform, schema['platforms']) self.assertEqual(expected_platform, schema['platforms'])
def testCallbackComment(self): def testCallbackComment(self):
schema = self.idl_basics schema = self.idl_basics
self.assertEqual('A comment on a callback.', self.assertEqual('A comment on a callback.',
getReturnsAsync(schema, 'function16')['description']) getReturnsAsync(schema, 'function16')['description'])
self.assertEqual( self.assertEqual(
'A parameter.', 'A parameter.',
getReturnsAsync(schema, 'function16')['parameters'][0]['description']) getReturnsAsync(schema, 'function16')['parameters'][0]['description'])
self.assertEqual( self.assertEqual(
'Just a parameter comment, with no comment on the callback.', 'Just a parameter comment, with no comment on the callback.',
getReturnsAsync(schema, 'function17')['parameters'][0]['description']) getReturnsAsync(schema, 'function17')['parameters'][0]['description'])
self.assertEqual( self.assertEqual('Override callback comment.',
'Override callback comment.', getReturnsAsync(schema, 'function18')['description'])
getReturnsAsync(schema, 'function18')['description'])
def testFunctionComment(self): def testFunctionComment(self):
schema = self.idl_basics schema = self.idl_basics
func = getFunction(schema, 'function3') func = getFunction(schema, 'function3')
self.assertEqual(('This comment should appear in the documentation, ' self.assertEqual(('This comment should appear in the documentation, '
'despite occupying multiple lines.'), 'despite occupying multiple lines.'), func['description'])
func['description']) self.assertEqual([{
self.assertEqual( 'description': ('So should this comment about the argument. '
[{'description': ('So should this comment about the argument. ' '<em>HTML</em> is fine too.'),
'<em>HTML</em> is fine too.'), 'name':
'name': 'arg', 'arg',
'$ref': 'MyType1'}], '$ref':
func['parameters']) 'MyType1'
}], func['parameters'])
func = getFunction(schema, 'function4') func = getFunction(schema, 'function4')
self.assertEqual( self.assertEqual(
'<p>This tests if "double-quotes" are escaped correctly.</p>' '<p>This tests if "double-quotes" are escaped correctly.</p>'
@@ -275,12 +373,18 @@ class IdlSchemaTest(unittest.TestCase):
schema = idl_schema.Load('test/idl_reserved_words.idl')[0] schema = idl_schema.Load('test/idl_reserved_words.idl')[0]
foo_type = getType(schema, 'Foo') foo_type = getType(schema, 'Foo')
self.assertEqual([{'name': 'float'}, {'name': 'DOMString'}], self.assertEqual([{
foo_type['enum']) 'name': 'float'
}, {
'name': 'DOMString'
}], foo_type['enum'])
enum_type = getType(schema, 'enum') enum_type = getType(schema, 'enum')
self.assertEqual([{'name': 'callback'}, {'name': 'namespace'}], self.assertEqual([{
enum_type['enum']) 'name': 'callback'
}, {
'name': 'namespace'
}], enum_type['enum'])
dictionary = getType(schema, 'dictionary') dictionary = getType(schema, 'dictionary')
self.assertEqual('integer', dictionary['properties']['long']['type']) self.assertEqual('integer', dictionary['properties']['long']['type'])
@@ -300,12 +404,10 @@ class IdlSchemaTest(unittest.TestCase):
self.assertEqual('integer', foo_type['properties']['x']['type']) self.assertEqual('integer', foo_type['properties']['x']['type'])
self.assertEqual('object', foo_type['properties']['y']['type']) self.assertEqual('object', foo_type['properties']['y']['type'])
self.assertEqual( self.assertEqual(
'any', 'any', foo_type['properties']['y']['additionalProperties']['type'])
foo_type['properties']['y']['additionalProperties']['type'])
self.assertEqual('object', foo_type['properties']['z']['type']) self.assertEqual('object', foo_type['properties']['z']['type'])
self.assertEqual( self.assertEqual(
'any', 'any', foo_type['properties']['z']['additionalProperties']['type'])
foo_type['properties']['z']['additionalProperties']['type'])
self.assertEqual('Window', foo_type['properties']['z']['isInstanceOf']) self.assertEqual('Window', foo_type['properties']['z']['isInstanceOf'])
bar_type = getType(schema, 'BarType') bar_type = getType(schema, 'BarType')
@@ -337,35 +439,48 @@ class IdlSchemaTest(unittest.TestCase):
union_type = getType(schema, 'UnionType') union_type = getType(schema, 'UnionType')
expected = { expected = {
'type': 'object', 'type': 'object',
'id': 'UnionType', 'id': 'UnionType',
'properties': { 'properties': {
'x': { 'x': {
'name': 'x', 'name': 'x',
'optional': True, 'optional': True,
'choices': [ 'choices': [
{'type': 'integer'}, {
{'$ref': 'FooType'}, 'type': 'integer'
] },
}, {
'y': { '$ref': 'FooType'
'name': 'y', },
'choices': [ ]
{'type': 'string'}, },
{'type': 'object', 'y': {
'additionalProperties': {'type': 'any'}} 'name':
] 'y',
}, 'choices': [{
'z': { 'type': 'string'
'name': 'z', }, {
'choices': [ 'type': 'object',
{'type': 'object', 'isInstanceOf': 'ImageData', 'additionalProperties': {
'additionalProperties': {'type': 'any'}}, 'type': 'any'
{'type': 'integer'} }
] }]
} },
}, 'z': {
} 'name':
'z',
'choices': [{
'type': 'object',
'isInstanceOf': 'ImageData',
'additionalProperties': {
'type': 'any'
}
}, {
'type': 'integer'
}]
}
},
}
self.assertEqual(expected, union_type) self.assertEqual(expected, union_type)
@@ -374,19 +489,20 @@ class IdlSchemaTest(unittest.TestCase):
union_type = getType(schema, 'ModifiedUnionType') union_type = getType(schema, 'ModifiedUnionType')
expected = { expected = {
'type': 'object', 'type': 'object',
'id': 'ModifiedUnionType', 'id': 'ModifiedUnionType',
'properties': { 'properties': {
'x': { 'x': {
'name': 'x', 'name': 'x',
'nodoc': True, 'nodoc': True,
'choices': [ 'choices': [{
{'type': 'integer'}, 'type': 'integer'
{'type': 'string'} }, {
] 'type': 'string'
} }]
} }
} }
}
self.assertEqual(expected, union_type) self.assertEqual(expected, union_type)
@@ -394,17 +510,17 @@ class IdlSchemaTest(unittest.TestCase):
schema = idl_schema.Load('test/idl_object_types.idl')[0] schema = idl_schema.Load('test/idl_object_types.idl')[0]
object_type = getType(schema, 'SerializableFunctionObject') object_type = getType(schema, 'SerializableFunctionObject')
expected = { expected = {
'type': 'object', 'type': 'object',
'id': 'SerializableFunctionObject', 'id': 'SerializableFunctionObject',
'properties': { 'properties': {
'func': { 'func': {
'name': 'func', 'name': 'func',
'serializableFunction': True, 'serializableFunction': True,
'type': 'function', 'type': 'function',
'parameters': [] 'parameters': []
} }
} }
} }
self.assertEqual(expected, object_type) self.assertEqual(expected, object_type)
def testUnionsWithFunctions(self): def testUnionsWithFunctions(self):
@@ -412,12 +528,13 @@ class IdlSchemaTest(unittest.TestCase):
union_params = getParams(schema, 'union_params') union_params = getParams(schema, 'union_params')
expected = [{ expected = [{
'name': 'x', 'name': 'x',
'choices': [ 'choices': [{
{'type': 'integer'}, 'type': 'integer'
{'type': 'string'} }, {
] 'type': 'string'
}] }]
}]
self.assertEqual(expected, union_params) self.assertEqual(expected, union_params)
@@ -426,24 +543,32 @@ class IdlSchemaTest(unittest.TestCase):
blah_params = getReturnsAsync(schema, 'blah') blah_params = getReturnsAsync(schema, 'blah')
expected = { expected = {
'name': 'callback', 'parameters': [{ 'name':
'name': 'x', 'callback',
'choices': [ 'parameters': [{
{'type': 'integer'}, 'name': 'x',
{'type': 'string'} 'choices': [{
]} 'type': 'integer'
] }, {
} 'type': 'string'
}]
}]
}
badabish_params = getReturnsAsync(schema, 'badabish') badabish_params = getReturnsAsync(schema, 'badabish')
expected = { expected = {
'name': 'callback', 'parameters': [{ 'name':
'name': 'x', 'optional': True, 'choices': [ 'callback',
{'type': 'integer'}, 'parameters': [{
{'type': 'string'} 'name': 'x',
] 'optional': True,
}] 'choices': [{
} 'type': 'integer'
}, {
'type': 'string'
}]
}]
}
self.assertEqual(expected, badabish_params) self.assertEqual(expected, badabish_params)
@@ -453,7 +578,10 @@ class IdlSchemaTest(unittest.TestCase):
expected_params = [] expected_params = []
expected_returns_async = { expected_returns_async = {
'name': 'callback', 'name': 'callback',
'parameters': [{'name': 'x', 'type': 'integer'}], 'parameters': [{
'name': 'x',
'type': 'integer'
}],
'does_not_support_promises': 'Test' 'does_not_support_promises': 'Test'
} }
params = getParams(schema, 'non_promise_supporting') params = getParams(schema, 'non_promise_supporting')
@@ -465,46 +593,60 @@ class IdlSchemaTest(unittest.TestCase):
def testFunctionWithoutPromiseSupportAndParams(self): def testFunctionWithoutPromiseSupportAndParams(self):
schema = idl_schema.Load('test/idl_function_types.idl')[0] schema = idl_schema.Load('test/idl_function_types.idl')[0]
expected_params = [ expected_params = [{
{'name': 'z', 'type': 'integer'}, 'name': 'z',
{'name': 'y', 'choices': [{'type': 'integer'}, {'type': 'string'}]} 'type': 'integer'
] }, {
'name': 'y',
'choices': [{
'type': 'integer'
}, {
'type': 'string'
}]
}]
expected_returns_async = { expected_returns_async = {
'name': 'callback', 'name': 'callback',
'parameters': [{'name': 'x', 'type': 'integer'}], 'parameters': [{
'name': 'x',
'type': 'integer'
}],
'does_not_support_promises': 'Test' 'does_not_support_promises': 'Test'
} }
params = getParams(schema, 'non_promise_supporting_with_params') params = getParams(schema, 'non_promise_supporting_with_params')
returns_async = getReturnsAsync( returns_async = getReturnsAsync(schema,
schema, 'non_promise_supporting_with_params' 'non_promise_supporting_with_params')
)
self.assertEqual(expected_params, params) self.assertEqual(expected_params, params)
self.assertEqual(expected_returns_async, returns_async) self.assertEqual(expected_returns_async, returns_async)
def testProperties(self): def testProperties(self):
schema = idl_schema.Load('test/idl_properties.idl')[0] schema = idl_schema.Load('test/idl_properties.idl')[0]
self.assertEqual(OrderedDict([ self.assertEqual(
('first', OrderedDict([ OrderedDict([
('description', 'Integer property.'), ('first',
('type', 'integer'), OrderedDict([
('value', 42), ('description', 'Integer property.'),
])), ('type', 'integer'),
('second', OrderedDict([ ('value', 42),
('description', 'Double property.'), ])),
('type', 'number'), ('second',
('value', 42.1), OrderedDict([
])), ('description', 'Double property.'),
('third', OrderedDict([ ('type', 'number'),
('description', 'String property.'), ('value', 42.1),
('type', 'string'), ])),
('value', 'hello world'), ('third',
])), OrderedDict([
('fourth', OrderedDict([ ('description', 'String property.'),
('description', 'Unvalued property.'), ('type', 'string'),
('type', 'integer'), ('value', 'hello world'),
])), ])),
]), schema.get('properties')) ('fourth',
OrderedDict([
('description', 'Unvalued property.'),
('type', 'integer'),
])),
]), schema.get('properties'))
def testManifestKeys(self): def testManifestKeys(self):
schema = self.idl_basics schema = self.idl_basics
@@ -512,19 +654,20 @@ class IdlSchemaTest(unittest.TestCase):
# exhaustive so we don't have to update it each time we add a new key in the # exhaustive so we don't have to update it each time we add a new key in the
# test file. # test file.
manifest_keys = schema.get('manifest_keys') manifest_keys = schema.get('manifest_keys')
self.assertEqual(manifest_keys['key_str'], self.assertEqual(
OrderedDict([('description', 'String manifest key.'), manifest_keys['key_str'],
('name', 'key_str'), OrderedDict([('description', 'String manifest key.'),
('type', 'string')])) ('name', 'key_str'), ('type', 'string')]))
self.assertEqual(manifest_keys['key_ref'], self.assertEqual(manifest_keys['key_ref'],
OrderedDict([('name', 'key_ref'), OrderedDict([('name', 'key_ref'), ('$ref', 'MyType2')])),
('$ref', 'MyType2')])), self.assertEqual(
self.assertEqual(manifest_keys['choice_with_arrays'], manifest_keys['choice_with_arrays'],
OrderedDict([('name', 'choice_with_arrays'), OrderedDict([('name', 'choice_with_arrays'),
('$ref', 'ChoiceWithArraysType')])), ('$ref', 'ChoiceWithArraysType')])),
self.assertEqual(manifest_keys['choice_with_optional'], self.assertEqual(
OrderedDict([('name', 'choice_with_optional'), manifest_keys['choice_with_optional'],
('$ref', 'ChoiceWithOptionalType')])) OrderedDict([('name', 'choice_with_optional'),
('$ref', 'ChoiceWithOptionalType')]))
def testNoManifestKeys(self): def testNoManifestKeys(self):
schema = idl_schema.Load('test/idl_properties.idl')[0] schema = idl_schema.Load('test/idl_properties.idl')[0]

@@ -23,11 +23,15 @@ NOTE = """// NOTE: The format of types has changed. 'FooType' is now
// See https://chromium.googlesource.com/chromium/src/+/main/docs/closure_compilation.md // See https://chromium.googlesource.com/chromium/src/+/main/docs/closure_compilation.md
""" """
class JsExternsGenerator(object): class JsExternsGenerator(object):
def Generate(self, namespace): def Generate(self, namespace):
return _Generator(namespace).Generate() return _Generator(namespace).Generate()
class _Generator(object): class _Generator(object):
def __init__(self, namespace): def __init__(self, namespace):
self._namespace = namespace self._namespace = namespace
self._class_name = None self._class_name = None
@@ -45,7 +49,7 @@ class _Generator(object):
src_to_script = os.path.relpath(script_dir, src_root) src_to_script = os.path.relpath(script_dir, src_root)
# tools/json_schema_compiler/compiler.py # tools/json_schema_compiler/compiler.py
compiler_path = os.path.join(src_to_script, 'compiler.py') compiler_path = os.path.join(src_to_script, 'compiler.py')
(c.Append(self._GetHeader(compiler_path, self._namespace.name)) (c.Append(self._GetHeader(compiler_path, self._namespace.name)) \
.Append()) .Append())
self._AppendNamespaceObject(c) self._AppendNamespaceObject(c)
@@ -69,13 +73,10 @@ class _Generator(object):
def _GetHeader(self, tool, namespace): def _GetHeader(self, tool, namespace):
"""Returns the file header text. """Returns the file header text.
""" """
return (self._js_util.GetLicense() + '\n' + return (self._js_util.GetLicense() + '\n' + self._js_util.GetInfo(tool) +
self._js_util.GetInfo(tool) + (NOTE % namespace) + '\n' + (NOTE % namespace) + '\n' + '/**\n' +
'/**\n' +
(' * @fileoverview Externs generated from namespace: %s\n' % (' * @fileoverview Externs generated from namespace: %s\n' %
namespace) + namespace) + ' * @externs\n' + ' */')
' * @externs\n' +
' */')
def _AppendType(self, c, js_type): def _AppendType(self, c, js_type):
"""Given a Type object, generates the Code for this type's definition. """Given a Type object, generates the Code for this type's definition.
@@ -95,9 +96,10 @@ class _Generator(object):
js_type.simple_name) js_type.simple_name)
c.Eblock(' */') c.Eblock(' */')
c.Append('%s.%s = {' % (self._GetNamespace(), js_type.name)) c.Append('%s.%s = {' % (self._GetNamespace(), js_type.name))
c.Append('\n'.join( c.Append('\n'.join([
[" %s: '%s'," % (self._js_util.GetPropertyName(v.name), v.name) " %s: '%s'," % (self._js_util.GetPropertyName(v.name), v.name)
for v in js_type.enum_values])) for v in js_type.enum_values
]))
c.Append('};') c.Append('};')
def _IsTypeConstructor(self, js_type): def _IsTypeConstructor(self, js_type):
@@ -120,8 +122,8 @@ class _Generator(object):
if js_type.property_type is not PropertyType.OBJECT: if js_type.property_type is not PropertyType.OBJECT:
self._js_util.AppendTypeJsDoc(c, self._namespace.name, js_type, optional) self._js_util.AppendTypeJsDoc(c, self._namespace.name, js_type, optional)
elif is_constructor: elif is_constructor:
c.Comment('@constructor', comment_prefix = '', wrap_indent=4) c.Comment('@constructor', comment_prefix='', wrap_indent=4)
c.Comment('@private', comment_prefix = '', wrap_indent=4) c.Comment('@private', comment_prefix='', wrap_indent=4)
else: else:
self._AppendTypedef(c, js_type.properties) self._AppendTypedef(c, js_type.properties)
@@ -151,8 +153,10 @@ class _Generator(object):
c.Append('@typedef {') c.Append('@typedef {')
if properties: if properties:
self._js_util.AppendObjectDefinition( self._js_util.AppendObjectDefinition(c,
c, self._namespace.name, properties, new_line=False) self._namespace.name,
properties,
new_line=False)
else: else:
c.Append('Object', new_line=False) c.Append('Object', new_line=False)
c.Append('}', new_line=False) c.Append('}', new_line=False)
@@ -178,8 +182,8 @@ class _Generator(object):
""" """
self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function) self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function)
params = self._GetFunctionParams(function) params = self._GetFunctionParams(function)
c.Append('%s.%s = function(%s) {};' % (self._GetNamespace(), c.Append('%s.%s = function(%s) {};' %
function.name, params)) (self._GetNamespace(), function.name, params))
c.Append() c.Append()
def _AppendEvent(self, c, event): def _AppendEvent(self, c, event):

@@ -496,15 +496,16 @@ chrome.fakeJson.funcWithReturnsAsync = function(someNumber, callback) {};""" % (
class JsExternGeneratorTest(unittest.TestCase): class JsExternGeneratorTest(unittest.TestCase):
def _GetNamespace(self, fake_content, filename, is_idl): def _GetNamespace(self, fake_content, filename, is_idl):
"""Returns a namespace object for the given content""" """Returns a namespace object for the given content"""
api_def = (idl_schema.Process(fake_content, filename) if is_idl api_def = (idl_schema.Process(fake_content, filename)
else json_parse.Parse(fake_content)) if is_idl else json_parse.Parse(fake_content))
m = model.Model() m = model.Model()
return m.AddNamespace(api_def[0], filename) return m.AddNamespace(api_def[0], filename)
def setUp(self): def setUp(self):
self.maxDiff = None # Lets us see the full diff when inequal. self.maxDiff = None # Lets us see the full diff when inequal.
def testBasic(self): def testBasic(self):
namespace = self._GetNamespace(fake_idl, 'fake_api.idl', True) namespace = self._GetNamespace(fake_idl, 'fake_api.idl', True)

@@ -15,11 +15,15 @@ import os
import sys import sys
import re import re
class JsInterfaceGenerator(object): class JsInterfaceGenerator(object):
def Generate(self, namespace): def Generate(self, namespace):
return _Generator(namespace).Generate() return _Generator(namespace).Generate()
class _Generator(object): class _Generator(object):
def __init__(self, namespace): def __init__(self, namespace):
self._namespace = namespace self._namespace = namespace
first = namespace.name[0].upper() first = namespace.name[0].upper()
@@ -31,7 +35,7 @@ class _Generator(object):
"""Generates a Code object with the schema for the entire namespace. """Generates a Code object with the schema for the entire namespace.
""" """
c = Code() c = Code()
(c.Append(self._GetHeader(sys.argv[0], self._namespace.name)) (c.Append(self._GetHeader(sys.argv[0], self._namespace.name)) \
.Append()) .Append())
self._AppendInterfaceObject(c) self._AppendInterfaceObject(c)
@@ -56,10 +60,10 @@ class _Generator(object):
def _GetHeader(self, tool, namespace): def _GetHeader(self, tool, namespace):
"""Returns the file header text. """Returns the file header text.
""" """
return ( return (self._js_util.GetLicense() + '\n' + self._js_util.GetInfo(tool) +
self._js_util.GetLicense() + '\n' + self._js_util.GetInfo(tool) + '\n' + '\n' +
('/** @fileoverview Interface for %s that can be overriden. */' % ('/** @fileoverview Interface for %s that can be overriden. */' %
namespace)) namespace))
def _AppendInterfaceObject(self, c): def _AppendInterfaceObject(self, c):
"""Appends the code creating the interface object. """Appends the code creating the interface object.
@@ -67,7 +71,7 @@ class _Generator(object):
/** @interface */ /** @interface */
function SettingsPrivate() {} function SettingsPrivate() {}
""" """
(c.Append('/** @interface */') (c.Append('/** @interface */') \
.Append('function %s() {}' % self._interface)) .Append('function %s() {}' % self._interface))
def _AppendFunction(self, c, function): def _AppendFunction(self, c, function):
@@ -88,8 +92,8 @@ class _Generator(object):
self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function) self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function)
c.Append('%s: function(%s) {},' % (function.name, ', '.join( c.Append('%s: function(%s) {},' %
getParamNames(function)))) (function.name, ', '.join(getParamNames(function))))
c.Append() c.Append()
def _AppendEvent(self, c, event): def _AppendEvent(self, c, event):

@@ -110,10 +110,12 @@ FakeApi.prototype = {
* @type {!ChromeEvent} * @type {!ChromeEvent}
* @see https://developer.chrome.com/extensions/fakeApi#event-onTrapDetected * @see https://developer.chrome.com/extensions/fakeApi#event-onTrapDetected
*/ */
FakeApi.prototype.onTrapDetected;""" % (datetime.now().year, FakeApi.prototype.onTrapDetected;""" %
sys.argv[0].replace('\\', '/'))) (datetime.now().year, sys.argv[0].replace('\\', '/')))
class JsExternGeneratorTest(unittest.TestCase): class JsExternGeneratorTest(unittest.TestCase):
def _GetNamespace(self, fake_content, filename): def _GetNamespace(self, fake_content, filename):
"""Returns a namespace object for the given content""" """Returns a namespace object for the given content"""
api_def = idl_schema.Process(fake_content, filename) api_def = idl_schema.Process(fake_content, filename)
@@ -121,7 +123,7 @@ class JsExternGeneratorTest(unittest.TestCase):
return m.AddNamespace(api_def[0], filename) return m.AddNamespace(api_def[0], filename)
def setUp(self): def setUp(self):
self.maxDiff = None # Lets us see the full diff when inequal. self.maxDiff = None # Lets us see the full diff when inequal.
def testBasic(self): def testBasic(self):
namespace = self._GetNamespace(fake_idl, 'fake_api.idl') namespace = self._GetNamespace(fake_idl, 'fake_api.idl')

@@ -21,6 +21,7 @@ INFO = """// This file was generated by:
class JsUtil(object): class JsUtil(object):
"""A helper class for generating JS Code. """A helper class for generating JS Code.
""" """
def GetLicense(self): def GetLicense(self):
"""Returns the license text for JS extern and interface files. """Returns the license text for JS extern and interface files.
""" """
@@ -31,7 +32,7 @@ class JsUtil(object):
""" """
return (INFO % tool.replace('\\', '/')) return (INFO % tool.replace('\\', '/'))
def GetPropertyName(self,e): def GetPropertyName(self, e):
# Enum properties are normified to be in ALL_CAPS_STYLE. # Enum properties are normified to be in ALL_CAPS_STYLE.
# Assume enum '1ring-rulesThemAll'. # Assume enum '1ring-rulesThemAll'.
# Transform to '1ring-rules_Them_All'. # Transform to '1ring-rules_Them_All'.
@@ -43,8 +44,11 @@ class JsUtil(object):
# Transform to '_1RING_RULES_THEM_ALL'. # Transform to '_1RING_RULES_THEM_ALL'.
return e.upper() return e.upper()
def AppendObjectDefinition(self, c, namespace_name, properties, def AppendObjectDefinition(self,
new_line=True): c,
namespace_name,
properties,
new_line=True):
"""Given an OrderedDict of properties, returns a Code containing the """Given an OrderedDict of properties, returns a Code containing the
description of an object. description of an object.
""" """
@@ -62,8 +66,8 @@ class JsUtil(object):
first = False first = False
js_type = self._TypeToJsType(namespace_name, prop.type_) js_type = self._TypeToJsType(namespace_name, prop.type_)
if prop.optional: if prop.optional:
js_type = (Code().Append('(') js_type = (Code().Append('(') \
.Concat(js_type, new_line=False) .Concat(js_type, new_line=False) \
.Append('|undefined)', new_line=False)) .Append('|undefined)', new_line=False))
c.Append('%s: ' % field, strip_right=False) c.Append('%s: ' % field, strip_right=False)
c.Concat(js_type, new_line=False) c.Concat(js_type, new_line=False)
@@ -88,16 +92,18 @@ class JsUtil(object):
c.Append('}', new_line=False) c.Append('}', new_line=False)
c.Comment(' %s' % name, comment_prefix='', wrap_indent=4, new_line=False) c.Comment(' %s' % name, comment_prefix='', wrap_indent=4, new_line=False)
if description: if description:
c.Comment(' %s' % description, comment_prefix='', c.Comment(' %s' % description,
wrap_indent=4, new_line=False) comment_prefix='',
wrap_indent=4,
new_line=False)
for i, param in enumerate(function.params): for i, param in enumerate(function.params):
# Mark the parameter as optional, *only if* all following parameters are # Mark the parameter as optional, *only if* all following parameters are
# also optional, to avoid JSC_OPTIONAL_ARG_AT_END errors thrown by Closure # also optional, to avoid JSC_OPTIONAL_ARG_AT_END errors thrown by Closure
# Compiler. # Compiler.
optional = ( optional = (all(p.optional for p in function.params[i:])
all(p.optional for p in function.params[i:]) and and (function.returns_async is None
(function.returns_async is None or function.returns_async.optional)) or function.returns_async.optional))
js_type = self._TypeToJsType(namespace_name, param.type_) js_type = self._TypeToJsType(namespace_name, param.type_)
# If the parameter was originally optional, but was followed by # If the parameter was originally optional, but was followed by
@@ -123,8 +129,8 @@ class JsUtil(object):
if function.returns: if function.returns:
append_field(c, 'return', append_field(c, 'return',
self._TypeToJsType(namespace_name, function.returns), self._TypeToJsType(namespace_name, function.returns), '',
'', False, function.returns.description) False, function.returns.description)
if function.deprecated: if function.deprecated:
c.Append('@deprecated %s' % function.deprecated) c.Append('@deprecated %s' % function.deprecated)
@@ -149,9 +155,8 @@ class JsUtil(object):
"""Converts a model.Function to a JS type (i.e., function([params])...)""" """Converts a model.Function to a JS type (i.e., function([params])...)"""
c = Code() c = Code()
c.Append('function(') c.Append('function(')
c.Concat( c.Concat(self._FunctionParamsToJsParams(namespace_name, function.params),
self._FunctionParamsToJsParams(namespace_name, function.params), new_line=False)
new_line=False)
c.Append('): ', new_line=False, strip_right=False) c.Append('): ', new_line=False, strip_right=False)
if function.returns: if function.returns:
@@ -169,9 +174,9 @@ class JsUtil(object):
# appended to the params as a callback. # appended to the params as a callback.
c = Code() c = Code()
c.Append('function(') c.Append('function(')
c.Concat( c.Concat(self._FunctionParamsToJsParams(namespace_name,
self._FunctionParamsToJsParams(namespace_name, returns_async.params), returns_async.params),
new_line=False) new_line=False)
c.Append('): ', new_line=False, strip_right=False) c.Append('): ', new_line=False, strip_right=False)
c.Append('void', new_line=False) c.Append('void', new_line=False)
@@ -211,10 +216,9 @@ class JsUtil(object):
return Code().Append(js_type.instance_of) return Code().Append(js_type.instance_of)
return Code().Append('Object') return Code().Append('Object')
if js_type.property_type is PropertyType.ARRAY: if js_type.property_type is PropertyType.ARRAY:
return (Code().Append('!Array<'). return (Code().Append('!Array<').Concat(
Concat(self._TypeToJsType(namespace_name, js_type.item_type), self._TypeToJsType(namespace_name, js_type.item_type),
new_line=False). new_line=False).Append('>', new_line=False))
Append('>', new_line=False))
if js_type.property_type is PropertyType.REF: if js_type.property_type is PropertyType.REF:
ref_type = '!chrome.%s.%s' % (namespace_name, js_type.ref_type) ref_type = '!chrome.%s.%s' % (namespace_name, js_type.ref_type)
return Code().Append(ref_type) return Code().Append(ref_type)
@@ -235,7 +239,7 @@ class JsUtil(object):
return Code().Append('*') return Code().Append('*')
if js_type.property_type.is_fundamental: if js_type.property_type.is_fundamental:
return Code().Append(js_type.property_type.name) return Code().Append(js_type.property_type.name)
return Code().Append('?') # TODO(tbreisacher): Make this more specific. return Code().Append('?') # TODO(tbreisacher): Make this more specific.
def AppendSeeLink(self, c, namespace_name, object_type, object_name): def AppendSeeLink(self, c, namespace_name, object_type, object_name):
"""Appends a @see link for a given API 'object' (type, method, or event). """Appends a @see link for a given API 'object' (type, method, or event).

@@ -9,8 +9,8 @@ import sys
_FILE_PATH = os.path.dirname(os.path.realpath(__file__)) _FILE_PATH = os.path.dirname(os.path.realpath(__file__))
_SYS_PATH = sys.path[:] _SYS_PATH = sys.path[:]
try: try:
_COMMENT_EATER_PATH = os.path.join( _COMMENT_EATER_PATH = os.path.join(_FILE_PATH, os.pardir,
_FILE_PATH, os.pardir, 'json_comment_eater') 'json_comment_eater')
sys.path.insert(0, _COMMENT_EATER_PATH) sys.path.insert(0, _COMMENT_EATER_PATH)
import json_comment_eater import json_comment_eater
finally: finally:
@@ -36,17 +36,14 @@ except ImportError:
_SYS_PATH = sys.path[:] _SYS_PATH = sys.path[:]
try: try:
_SIMPLE_JSON_PATH = os.path.join(_FILE_PATH, _SIMPLE_JSON_PATH = os.path.join(_FILE_PATH, os.pardir, os.pardir,
os.pardir,
os.pardir,
'third_party') 'third_party')
sys.path.insert(0, _SIMPLE_JSON_PATH) sys.path.insert(0, _SIMPLE_JSON_PATH)
# Add this path in case this is being used in the docs server. # Add this path in case this is being used in the docs server.
sys.path.insert(0, os.path.join(_FILE_PATH, sys.path.insert(
os.pardir, 0,
os.pardir, os.path.join(_FILE_PATH, os.pardir, os.pardir, 'third_party',
'third_party', 'json_schema_compiler'))
'json_schema_compiler'))
import simplejson import simplejson
from simplejson import OrderedDict from simplejson import OrderedDict
finally: finally:

@@ -17,8 +17,8 @@ def DeleteNodes(item, delete_key=None, matcher=None):
def ShouldDelete(thing): def ShouldDelete(thing):
return json_parse.IsDict(thing) and ( return json_parse.IsDict(thing) and (
delete_key is not None and delete_key in thing or delete_key is not None and delete_key in thing
matcher is not None and matcher(thing)) or matcher is not None and matcher(thing))
if json_parse.IsDict(item): if json_parse.IsDict(item):
toDelete = [] toDelete = []
@@ -30,8 +30,10 @@ def DeleteNodes(item, delete_key=None, matcher=None):
for key in toDelete: for key in toDelete:
del item[key] del item[key]
elif type(item) == list: elif type(item) == list:
item[:] = [DeleteNodes(thing, delete_key, matcher) item[:] = [
for thing in item if not ShouldDelete(thing)] DeleteNodes(thing, delete_key, matcher) for thing in item
if not ShouldDelete(thing)
]
return item return item

@@ -6,93 +6,85 @@
import json_schema import json_schema
import unittest import unittest
class JsonSchemaUnittest(unittest.TestCase): class JsonSchemaUnittest(unittest.TestCase):
def testNocompile(self): def testNocompile(self):
compiled = [ compiled = [{
{
"namespace": "compile", "namespace": "compile",
"description": "The compile API.", "description": "The compile API.",
"functions": [], "functions": [],
"types": {} "types": {}
}, }, {
{
"namespace": "functions", "namespace": "functions",
"description": "The functions API.", "description": "The functions API.",
"functions": [ "functions": [{
{
"id": "two" "id": "two"
}, }, {
{
"id": "four" "id": "four"
} }],
],
"types": { "types": {
"one": { "key": "value" } "one": {
"key": "value"
}
} }
}, }, {
{
"namespace": "types", "namespace": "types",
"description": "The types API.", "description": "The types API.",
"functions": [ "functions": [{
{ "id": "one" } "id": "one"
], }],
"types": { "types": {
"two": { "two": {
"key": "value" "key": "value"
}, },
"four": { "four": {
"key": "value" "key": "value"
} }
} }
}, }, {
{
"namespace": "nested", "namespace": "nested",
"description": "The nested API.", "description": "The nested API.",
"properties": { "properties": {
"sync": { "sync": {
"functions": [ "functions": [{
{ "id": "two"
"id": "two" }, {
}, "id": "four"
{ }],
"id": "four" "types": {
} "two": {
], "key": "value"
"types": { },
"two": { "four": {
"key": "value" "key": "value"
}, }
"four": { }
"key": "value"
}
} }
}
} }
} }]
]
schema = json_schema.CachedLoad('test/json_schema_test.json') schema = json_schema.CachedLoad('test/json_schema_test.json')
self.assertEqual(compiled, json_schema.DeleteNodes(schema, 'nocompile')) self.assertEqual(compiled, json_schema.DeleteNodes(schema, 'nocompile'))
def should_delete(value): def should_delete(value):
return isinstance(value, dict) and not value.get('valid', True) return isinstance(value, dict) and not value.get('valid', True)
expected = [
{'one': {'test': 'test'}}, expected = [{'one': {'test': 'test'}}, {'valid': True}, {}]
{'valid': True}, given = [{
{} 'one': {
] 'test': 'test'
given = [ },
{'one': {'test': 'test'}, 'two': {'valid': False}}, 'two': {
{'valid': True}, 'valid': False
{}, }
{'valid': False} }, {
] 'valid': True
self.assertEqual( }, {}, {
expected, json_schema.DeleteNodes(given, matcher=should_delete)) 'valid': False
}]
self.assertEqual(expected,
json_schema.DeleteNodes(given, matcher=should_delete))
if __name__ == '__main__': if __name__ == '__main__':

@@ -2,13 +2,16 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
def memoize(fn): def memoize(fn):
'''Decorates |fn| to memoize. '''Decorates |fn| to memoize.
''' '''
memory = {} memory = {}
def impl(*args, **optargs): def impl(*args, **optargs):
full_args = args + tuple(optargs.items()) full_args = args + tuple(optargs.items())
if full_args not in memory: if full_args not in memory:
memory[full_args] = fn(*args, **optargs) memory[full_args] = fn(*args, **optargs)
return memory[full_args] return memory[full_args]
return impl return impl

@@ -17,14 +17,16 @@ def _IsTypeFromManifestKeys(namespace, typename, fallback):
return fallback return fallback
class ParseException(Exception): class ParseException(Exception):
"""Thrown when data in the model is invalid. """Thrown when data in the model is invalid.
""" """
def __init__(self, parent, message): def __init__(self, parent, message):
hierarchy = _GetModelHierarchy(parent) hierarchy = _GetModelHierarchy(parent)
hierarchy.append(message) hierarchy.append(message)
Exception.__init__( Exception.__init__(self,
self, 'Model parse exception at:\n' + '\n'.join(hierarchy)) 'Model parse exception at:\n' + '\n'.join(hierarchy))
class Model(object): class Model(object):
@@ -33,6 +35,7 @@ class Model(object):
Properties: Properties:
- |namespaces| a map of a namespace name to its model.Namespace - |namespaces| a map of a namespace name to its model.Namespace
""" """
def __init__(self, allow_inline_enums=True): def __init__(self, allow_inline_enums=True):
self._allow_inline_enums = allow_inline_enums self._allow_inline_enums = allow_inline_enums
self.namespaces = {} self.namespaces = {}
@@ -67,11 +70,13 @@ class ComplexFeature(object):
- |unix_name| the unix_name of the feature - |unix_name| the unix_name of the feature
- |feature_list| a list of simple features which make up the feature - |feature_list| a list of simple features which make up the feature
""" """
def __init__(self, feature_name, features): def __init__(self, feature_name, features):
self.name = feature_name self.name = feature_name
self.unix_name = UnixName(self.name) self.unix_name = UnixName(self.name)
self.feature_list = features self.feature_list = features
class SimpleFeature(object): class SimpleFeature(object):
"""A simple feature, which can make up a complex feature, as specified in """A simple feature, which can make up a complex feature, as specified in
files such as chrome/common/extensions/api/_permission_features.json. files such as chrome/common/extensions/api/_permission_features.json.
@@ -83,6 +88,7 @@ class SimpleFeature(object):
- |extension_types| the types which can use the feature - |extension_types| the types which can use the feature
- |allowlist| a list of extensions allowed to use the feature - |allowlist| a list of extensions allowed to use the feature
""" """
def __init__(self, feature_name, feature_def): def __init__(self, feature_name, feature_def):
self.name = feature_name self.name = feature_name
self.unix_name = UnixName(self.name) self.unix_name = UnixName(self.name)
@@ -112,6 +118,7 @@ class Namespace(object):
|include_compiler_options| is True |include_compiler_options| is True
- |manifest_keys| is a Type representing the manifest keys for this namespace. - |manifest_keys| is a Type representing the manifest keys for this namespace.
""" """
def __init__(self, def __init__(self,
json, json,
source_file, source_file,
@@ -122,7 +129,7 @@ class Namespace(object):
if 'description' not in json: if 'description' not in json:
# TODO(kalman): Go back to throwing an error here. # TODO(kalman): Go back to throwing an error here.
print('%s must have a "description" field. This will appear ' print('%s must have a "description" field. This will appear '
'on the API summary page.' % self.name) 'on the API summary page.' % self.name)
json['description'] = '' json['description'] = ''
self.description = json['description'] self.description = json['description']
self.nodoc = json.get('nodoc', False) self.nodoc = json.get('nodoc', False)
@@ -198,12 +205,8 @@ class Type(object):
- |additional_properties| the type of the additional properties, if any is - |additional_properties| the type of the additional properties, if any is
specified specified
""" """
def __init__(self,
parent, def __init__(self, parent, name, json, namespace, input_origin):
name,
json,
namespace,
input_origin):
self.name = name self.name = name
# The typename "ManifestKeys" is reserved. # The typename "ManifestKeys" is reserved.
@@ -221,8 +224,9 @@ class Type(object):
# We need to do this to ensure types reference by manifest types have the # We need to do this to ensure types reference by manifest types have the
# correct value for |origin.from_manifest_keys|. # correct value for |origin.from_manifest_keys|.
self.origin = Origin( self.origin = Origin(
input_origin.from_client, input_origin.from_json, input_origin.from_client, input_origin.from_json,
_IsTypeFromManifestKeys(namespace, name, input_origin.from_manifest_keys)) _IsTypeFromManifestKeys(namespace, name,
input_origin.from_manifest_keys))
self.parent = parent self.parent = parent
self.instance_of = json.get('isInstanceOf', None) self.instance_of = json.get('isInstanceOf', None)
@@ -252,7 +256,7 @@ class Type(object):
raise ParseException( raise ParseException(
self, self,
'Inline enum "%s" found in namespace "%s". These are not allowed. ' 'Inline enum "%s" found in namespace "%s". These are not allowed. '
'See crbug.com/472279' % (name, namespace.name)) 'See crbug.com/472279' % (name, namespace.name))
self.property_type = PropertyType.ENUM self.property_type = PropertyType.ENUM
self.enum_values = [EnumValue(value, namespace) for value in json['enum']] self.enum_values = [EnumValue(value, namespace) for value in json['enum']]
self.cpp_enum_prefix_override = json.get('cpp_enum_prefix_override', None) self.cpp_enum_prefix_override = json.get('cpp_enum_prefix_override', None)
@@ -264,13 +268,13 @@ class Type(object):
self.property_type = PropertyType.BOOLEAN self.property_type = PropertyType.BOOLEAN
elif json_type == 'integer': elif json_type == 'integer':
self.property_type = PropertyType.INTEGER self.property_type = PropertyType.INTEGER
elif (json_type == 'double' or elif (json_type == 'double' or json_type == 'number'):
json_type == 'number'):
self.property_type = PropertyType.DOUBLE self.property_type = PropertyType.DOUBLE
elif json_type == 'string': elif json_type == 'string':
self.property_type = PropertyType.STRING self.property_type = PropertyType.STRING
elif 'choices' in json: elif 'choices' in json:
self.property_type = PropertyType.CHOICES self.property_type = PropertyType.CHOICES
def generate_type_name(type_json): def generate_type_name(type_json):
if 'items' in type_json: if 'items' in type_json:
return '%ss' % generate_type_name(type_json['items']) return '%ss' % generate_type_name(type_json['items'])
@@ -279,28 +283,22 @@ class Type(object):
if 'type' in type_json: if 'type' in type_json:
return type_json['type'] return type_json['type']
return None return None
self.choices = [ self.choices = [
Type(self, Type(self,
generate_type_name(choice) or 'choice%s' % i, generate_type_name(choice) or 'choice%s' % i, choice, namespace,
choice, self.origin) for i, choice in enumerate(json['choices'])
namespace, ]
self.origin)
for i, choice in enumerate(json['choices'])]
elif json_type == 'object': elif json_type == 'object':
if not ( if not ('isInstanceOf' in json or 'properties' in json
'isInstanceOf' in json or or 'additionalProperties' in json or 'functions' in json
'properties' in json or or 'events' in json):
'additionalProperties' in json or
'functions' in json or
'events' in json):
raise ParseException(self, name + " has no properties or functions") raise ParseException(self, name + " has no properties or functions")
self.property_type = PropertyType.OBJECT self.property_type = PropertyType.OBJECT
additional_properties_json = json.get('additionalProperties', None) additional_properties_json = json.get('additionalProperties', None)
if additional_properties_json is not None: if additional_properties_json is not None:
self.additional_properties = Type(self, self.additional_properties = Type(self, 'additionalProperties',
'additionalProperties', additional_properties_json, namespace,
additional_properties_json,
namespace,
self.origin) self.origin)
else: else:
self.additional_properties = None self.additional_properties = None
@@ -309,8 +307,8 @@ class Type(object):
# Sometimes we might have an unnamed function, e.g. if it's a property # Sometimes we might have an unnamed function, e.g. if it's a property
# of an object. Use the name of the property in that case. # of an object. Use the name of the property in that case.
function_name = json.get('name', name) function_name = json.get('name', name)
self.function = Function( self.function = Function(self, function_name, json, namespace,
self, function_name, json, namespace, self.origin) self.origin)
else: else:
raise ParseException(self, 'Unsupported JSON type %s' % json_type) raise ParseException(self, 'Unsupported JSON type %s' % json_type)
@@ -321,6 +319,7 @@ class Type(object):
''' '''
return self.name == 'ManifestKeys' return self.name == 'ManifestKeys'
class Function(object): class Function(object):
"""A Function defined in the API. """A Function defined in the API.
@@ -340,12 +339,8 @@ class Function(object):
- |returns| the return type of the function; None if the function does not - |returns| the return type of the function; None if the function does not
return a value return a value
""" """
def __init__(self,
parent, def __init__(self, parent, name, json, namespace, origin):
name,
json,
namespace,
origin):
self.name = name self.name = name
self.simple_name = _StripNamespace(self.name, namespace) self.simple_name = _StripNamespace(self.name, namespace)
self.platforms = _GetPlatforms(json) self.platforms = _GetPlatforms(json)
@@ -367,8 +362,10 @@ class Function(object):
def GeneratePropertyFromParam(p): def GeneratePropertyFromParam(p):
return Property(self, p['name'], p, namespace, origin) return Property(self, p['name'], p, namespace, origin)
self.filters = [GeneratePropertyFromParam(filter_instance) self.filters = [
for filter_instance in json.get('filters', [])] GeneratePropertyFromParam(filter_instance)
for filter_instance in json.get('filters', [])
]
# Any asynchronous return should be defined using the returns_async field. # Any asynchronous return should be defined using the returns_async field.
returns_async = json.get('returns_async', None) returns_async = json.get('returns_async', None)
@@ -383,14 +380,11 @@ class Function(object):
# incompatible with returning a promise. There are APIs that specify this, # incompatible with returning a promise. There are APIs that specify this,
# though, so we make sure they have specified does_not_support_promises if # though, so we make sure they have specified does_not_support_promises if
# they do. # they do.
if ( if (json.get('returns') is not None
json.get('returns') is not None and self.returns_async.can_return_promise):
and self.returns_async.can_return_promise
):
raise ValueError( raise ValueError(
'Cannot specify both returns and returns_async on a function ' 'Cannot specify both returns and returns_async on a function '
'which supports promies: %s.%s' % (namespace.name, name) 'which supports promies: %s.%s' % (namespace.name, name))
)
params = json.get('parameters', []) params = json.get('parameters', [])
for i, param in enumerate(params): for i, param in enumerate(params):
@@ -398,11 +392,8 @@ class Function(object):
self.returns = None self.returns = None
if 'returns' in json: if 'returns' in json:
self.returns = Type(self, self.returns = Type(self, '%sReturnType' % name, json['returns'],
'%sReturnType' % name, namespace, origin)
json['returns'],
namespace,
origin)
class ReturnsAsync(object): class ReturnsAsync(object):
@@ -423,6 +414,7 @@ class ReturnsAsync(object):
callback. Currently only consumed for documentation callback. Currently only consumed for documentation
purposes purposes
""" """
def __init__(self, parent, json, namespace, origin): def __init__(self, parent, json, namespace, origin):
self.name = json.get('name') self.name = json.get('name')
self.simple_name = _StripNamespace(self.name, namespace) self.simple_name = _StripNamespace(self.name, namespace)
@@ -434,25 +426,21 @@ class ReturnsAsync(object):
if json.get('returns') is not None: if json.get('returns') is not None:
raise ValueError( raise ValueError(
'Cannot return a value from an asynchronous return: %s.%s in %s' 'Cannot return a value from an asynchronous return: %s.%s in %s' %
% (namespace.name, parent.name, namespace.source_file) (namespace.name, parent.name, namespace.source_file))
)
if json.get('deprecated') is not None: if json.get('deprecated') is not None:
raise ValueError( raise ValueError(
'Cannot specify deprecated on an asynchronous return: %s.%s in %s' 'Cannot specify deprecated on an asynchronous return: %s.%s in %s' %
% (namespace.name, parent.name, namespace.source_file) (namespace.name, parent.name, namespace.source_file))
)
if json.get('parameters') is None: if json.get('parameters') is None:
raise ValueError( raise ValueError(
'parameters key not specified on returns_async: %s.%s in %s' 'parameters key not specified on returns_async: %s.%s in %s' %
% (namespace.name, parent.name, namespace.source_file) (namespace.name, parent.name, namespace.source_file))
)
if len(json.get('parameters')) > 1 and self.can_return_promise: if len(json.get('parameters')) > 1 and self.can_return_promise:
raise ValueError( raise ValueError(
'Only a single parameter can be specific on a returns_async which' 'Only a single parameter can be specific on a returns_async which'
' supports promises: %s.%s in %s' ' supports promises: %s.%s in %s' %
% (namespace.name, parent.name, namespace.source_file) (namespace.name, parent.name, namespace.source_file))
)
def GeneratePropertyFromParam(p): def GeneratePropertyFromParam(p):
return Property(self, p['name'], p, namespace, origin) return Property(self, p['name'], p, namespace, origin)
@@ -475,6 +463,7 @@ class Property(object):
- |simple_name| the name of this Property without a namespace - |simple_name| the name of this Property without a namespace
- |deprecated| a reason and possible alternative for a deprecated property - |deprecated| a reason and possible alternative for a deprecated property
""" """
def __init__(self, parent, name, json, namespace, origin): def __init__(self, parent, name, json, namespace, origin):
"""Creates a Property from JSON. """Creates a Property from JSON.
""" """
@@ -491,11 +480,10 @@ class Property(object):
self.nodoc = json.get('nodoc', False) self.nodoc = json.get('nodoc', False)
# HACK: only support very specific value types. # HACK: only support very specific value types.
is_allowed_value = ( is_allowed_value = ('$ref' not in json
'$ref' not in json and and ('type' not in json or json['type'] == 'integer'
('type' not in json or json['type'] == 'integer' or json['type'] == 'number'
or json['type'] == 'number' or json['type'] == 'string'))
or json['type'] == 'string'))
self.value = None self.value = None
if 'value' in json and is_allowed_value: if 'value' in json and is_allowed_value:
@@ -532,20 +520,21 @@ class Property(object):
if unix_name == self._unix_name: if unix_name == self._unix_name:
return return
if self._unix_name_used: if self._unix_name_used:
raise AttributeError( raise AttributeError('Cannot set the unix_name on %s; '
'Cannot set the unix_name on %s; ' 'it is already used elsewhere as %s' %
'it is already used elsewhere as %s' % (self.name, self._unix_name))
(self.name, self._unix_name))
self._unix_name = unix_name self._unix_name = unix_name
unix_name = property(GetUnixName, SetUnixName) unix_name = property(GetUnixName, SetUnixName)
class EnumValue(object): class EnumValue(object):
"""A single value from an enum. """A single value from an enum.
Properties: Properties:
- |name| name of the property as in the json. - |name| name of the property as in the json.
- |description| a description of the property (if provided) - |description| a description of the property (if provided)
""" """
def __init__(self, json, namespace): def __init__(self, json, namespace):
if isinstance(json, dict): if isinstance(json, dict):
self.name = json['name'] self.name = json['name']
@@ -556,17 +545,18 @@ class EnumValue(object):
# Using empty string values as enum key is only allowed in a few namespaces, # Using empty string values as enum key is only allowed in a few namespaces,
# as an exception to the rule, and we should not add more. # as an exception to the rule, and we should not add more.
if (not self.name and if (not self.name and namespace.name not in ['enums', 'webstorePrivate']):
namespace.name not in ['enums', 'webstorePrivate']):
raise ValueError('Enum value cannot be an empty string') raise ValueError('Enum value cannot be an empty string')
def CamelName(self): def CamelName(self):
return CamelName(self.name) return CamelName(self.name)
class _Enum(object): class _Enum(object):
"""Superclass for enum types with a "name" field, setting up repr/eq/ne. """Superclass for enum types with a "name" field, setting up repr/eq/ne.
Enums need to do this so that equality/non-equality work over pickling. Enums need to do this so that equality/non-equality work over pickling.
""" """
@staticmethod @staticmethod
def GetAll(cls): def GetAll(cls):
"""Yields all _Enum objects declared in |cls|. """Yields all _Enum objects declared in |cls|.
@@ -581,6 +571,7 @@ class _Enum(object):
def __eq__(self, other): def __eq__(self, other):
return type(other) == type(self) and other.name == self.name return type(other) == type(self) and other.name == self.name
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
@@ -595,6 +586,7 @@ class _Enum(object):
class _PropertyTypeInfo(_Enum): class _PropertyTypeInfo(_Enum):
def __init__(self, is_fundamental, name): def __init__(self, is_fundamental, name):
_Enum.__init__(self, name) _Enum.__init__(self, name)
self.is_fundamental = is_fundamental self.is_fundamental = is_fundamental
@@ -602,6 +594,7 @@ class _PropertyTypeInfo(_Enum):
def __repr__(self): def __repr__(self):
return self.name return self.name
class PropertyType(object): class PropertyType(object):
"""Enum of different types of properties/parameters. """Enum of different types of properties/parameters.
""" """
@@ -619,111 +612,113 @@ class PropertyType(object):
REF = _PropertyTypeInfo(False, "ref") REF = _PropertyTypeInfo(False, "ref")
STRING = _PropertyTypeInfo(True, "string") STRING = _PropertyTypeInfo(True, "string")
def IsCPlusPlusKeyword(name): def IsCPlusPlusKeyword(name):
"""Returns true if `name` is a C++ reserved keyword. """Returns true if `name` is a C++ reserved keyword.
""" """
# Obtained from https://en.cppreference.com/w/cpp/keyword. # Obtained from https://en.cppreference.com/w/cpp/keyword.
keywords = { keywords = {
"alignas", "alignas",
"alignof", "alignof",
"and", "and",
"and_eq", "and_eq",
"asm", "asm",
"atomic_cancel", "atomic_cancel",
"atomic_commit", "atomic_commit",
"atomic_noexcept", "atomic_noexcept",
"auto", "auto",
"bitand", "bitand",
"bitor", "bitor",
"bool", "bool",
"break", "break",
"case", "case",
"catch", "catch",
"char", "char",
"char8_t", "char8_t",
"char16_t", "char16_t",
"char32_t", "char32_t",
"class", "class",
"compl", "compl",
"concept", "concept",
"const", "const",
"consteval", "consteval",
"constexpr", "constexpr",
"constinit", "constinit",
"const_cast", "const_cast",
"continue", "continue",
"co_await", "co_await",
"co_return", "co_return",
"co_yield", "co_yield",
"decltype", "decltype",
"default", "default",
"delete", "delete",
"do", "do",
"double", "double",
"dynamic_cast", "dynamic_cast",
"else", "else",
"enum", "enum",
"explicit", "explicit",
"export", "export",
"extern", "extern",
"false", "false",
"float", "float",
"for", "for",
"friend", "friend",
"goto", "goto",
"if", "if",
"inline", "inline",
"int", "int",
"long", "long",
"mutable", "mutable",
"namespace", "namespace",
"new", "new",
"noexcept", "noexcept",
"not", "not",
"not_eq", "not_eq",
"nullptr", "nullptr",
"operator", "operator",
"or", "or",
"or_eq", "or_eq",
"private", "private",
"protected", "protected",
"public", "public",
"reflexpr", "reflexpr",
"register", "register",
"reinterpret_cast", "reinterpret_cast",
"requires", "requires",
"return", "return",
"short", "short",
"signed", "signed",
"sizeof", "sizeof",
"static", "static",
"static_assert", "static_assert",
"static_cast", "static_cast",
"struct", "struct",
"switch", "switch",
"synchronized", "synchronized",
"template", "template",
"this", "this",
"thread_local", "thread_local",
"throw", "throw",
"true", "true",
"try", "try",
"typedef", "typedef",
"typeid", "typeid",
"typename", "typename",
"union", "union",
"unsigned", "unsigned",
"using", "using",
"virtual", "virtual",
"void", "void",
"volatile", "volatile",
"wchar_t", "wchar_t",
"while", "while",
"xor", "xor",
"xor_eq" "xor_eq",
} }
return name in keywords return name in keywords
@memoize @memoize
def UnixName(name): def UnixName(name):
'''Returns the unix_style name for a given lowerCamelCase string. '''Returns the unix_style name for a given lowerCamelCase string.
@@ -740,7 +735,7 @@ def UnixName(name):
# Prepend an extra underscore to the |name|'s start if it doesn't start with a # Prepend an extra underscore to the |name|'s start if it doesn't start with a
# letter or underscore to ensure the generated unix name follows C++ # letter or underscore to ensure the generated unix name follows C++
# identifier rules. # identifier rules.
assert(name) assert (name)
if name[0].isdigit(): if name[0].isdigit():
name = '_' + name name = '_' + name
@@ -811,10 +806,7 @@ def _GetFunctions(parent, json, namespace):
""" """
functions = OrderedDict() functions = OrderedDict()
for function_json in json.get('functions', []): for function_json in json.get('functions', []):
function = Function(parent, function = Function(parent, function_json['name'], function_json, namespace,
function_json['name'],
function_json,
namespace,
Origin(from_json=True)) Origin(from_json=True))
functions[function.name] = function functions[function.name] = function
return functions return functions
@@ -825,10 +817,7 @@ def _GetEvents(parent, json, namespace):
""" """
events = OrderedDict() events = OrderedDict()
for event_json in json.get('events', []): for event_json in json.get('events', []):
event = Function(parent, event = Function(parent, event_json['name'], event_json, namespace,
event_json['name'],
event_json,
namespace,
Origin(from_client=True)) Origin(from_client=True))
events[event.name] = event events[event.name] = event
return events return events
@@ -853,12 +842,13 @@ def _GetManifestKeysType(self, json):
# Create a dummy object to parse "manifest_keys" as a type. # Create a dummy object to parse "manifest_keys" as a type.
manifest_keys_type = { manifest_keys_type = {
'type': 'object', 'type': 'object',
'properties': json['manifest_keys'], 'properties': json['manifest_keys'],
} }
return Type(self, 'ManifestKeys', manifest_keys_type, self, return Type(self, 'ManifestKeys', manifest_keys_type, self,
Origin(from_manifest_keys=True)) Origin(from_manifest_keys=True))
def _GetWithDefaultChecked(self, json, key, default): def _GetWithDefaultChecked(self, json, key, default):
if json.get(key) == default: if json.get(key) == default:
raise ParseException( raise ParseException(
@@ -867,7 +857,9 @@ def _GetWithDefaultChecked(self, json, key, default):
% (key, default)) % (key, default))
return json.get(key, default) return json.get(key, default)
class _PlatformInfo(_Enum): class _PlatformInfo(_Enum):
def __init__(self, name): def __init__(self, name):
_Enum.__init__(self, name) _Enum.__init__(self, name)

@@ -9,63 +9,62 @@ from model import Platforms
import model import model
import unittest import unittest
class ModelTest(unittest.TestCase): class ModelTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.model = model.Model() self.model = model.Model()
self.permissions_json = CachedLoad('test/permissions.json') self.permissions_json = CachedLoad('test/permissions.json')
self.model.AddNamespace(self.permissions_json[0], self.model.AddNamespace(self.permissions_json[0],
'path/to/permissions.json') 'path/to/permissions.json')
self.permissions = self.model.namespaces.get('permissions') self.permissions = self.model.namespaces.get('permissions')
self.windows_json = CachedLoad('test/windows.json') self.windows_json = CachedLoad('test/windows.json')
self.model.AddNamespace(self.windows_json[0], self.model.AddNamespace(self.windows_json[0], 'path/to/window.json')
'path/to/window.json')
self.windows = self.model.namespaces.get('windows') self.windows = self.model.namespaces.get('windows')
self.tabs_json = CachedLoad('test/tabs.json') self.tabs_json = CachedLoad('test/tabs.json')
self.model.AddNamespace(self.tabs_json[0], self.model.AddNamespace(self.tabs_json[0], 'path/to/tabs.json')
'path/to/tabs.json')
self.tabs = self.model.namespaces.get('tabs') self.tabs = self.model.namespaces.get('tabs')
self.idl_chromeos = Load('test/idl_namespace_chromeos.idl') self.idl_chromeos = Load('test/idl_namespace_chromeos.idl')
self.model.AddNamespace(self.idl_chromeos[0], self.model.AddNamespace(self.idl_chromeos[0],
'path/to/idl_namespace_chromeos.idl') 'path/to/idl_namespace_chromeos.idl')
self.idl_namespace_chromeos = self.model.namespaces.get( self.idl_namespace_chromeos = self.model.namespaces.get(
'idl_namespace_chromeos') 'idl_namespace_chromeos')
self.idl_all_platforms = Load('test/idl_namespace_all_platforms.idl') self.idl_all_platforms = Load('test/idl_namespace_all_platforms.idl')
self.model.AddNamespace(self.idl_all_platforms[0], self.model.AddNamespace(self.idl_all_platforms[0],
'path/to/idl_namespace_all_platforms.idl') 'path/to/idl_namespace_all_platforms.idl')
self.idl_namespace_all_platforms = self.model.namespaces.get( self.idl_namespace_all_platforms = self.model.namespaces.get(
'idl_namespace_all_platforms') 'idl_namespace_all_platforms')
self.idl_non_specific_platforms = Load( self.idl_non_specific_platforms = Load(
'test/idl_namespace_non_specific_platforms.idl') 'test/idl_namespace_non_specific_platforms.idl')
self.model.AddNamespace(self.idl_non_specific_platforms[0], self.model.AddNamespace(self.idl_non_specific_platforms[0],
'path/to/idl_namespace_non_specific_platforms.idl') 'path/to/idl_namespace_non_specific_platforms.idl')
self.idl_namespace_non_specific_platforms = self.model.namespaces.get( self.idl_namespace_non_specific_platforms = self.model.namespaces.get(
'idl_namespace_non_specific_platforms') 'idl_namespace_non_specific_platforms')
self.returns_async_json = CachedLoad('test/returns_async.json') self.returns_async_json = CachedLoad('test/returns_async.json')
self.model.AddNamespace(self.returns_async_json[0], self.model.AddNamespace(self.returns_async_json[0],
'path/to/returns_async.json') 'path/to/returns_async.json')
self.returns_async = self.model.namespaces.get('returns_async') self.returns_async = self.model.namespaces.get('returns_async')
self.idl_returns_async_idl = Load('test/idl_returns_async.idl') self.idl_returns_async_idl = Load('test/idl_returns_async.idl')
self.model.AddNamespace(self.idl_returns_async_idl[0], self.model.AddNamespace(self.idl_returns_async_idl[0],
'path/to/idl_returns_async.idl') 'path/to/idl_returns_async.idl')
self.idl_returns_async = self.model.namespaces.get('idl_returns_async') self.idl_returns_async = self.model.namespaces.get('idl_returns_async')
self.nodoc_json = CachedLoad('test/namespace_nodoc.json') self.nodoc_json = CachedLoad('test/namespace_nodoc.json')
self.model.AddNamespace(self.nodoc_json[0], self.model.AddNamespace(self.nodoc_json[0], 'path/to/namespace_nodoc.json')
'path/to/namespace_nodoc.json')
self.nodoc = self.model.namespaces.get('nodoc') self.nodoc = self.model.namespaces.get('nodoc')
self.fakeapi_json = CachedLoad('test/namespace_fakeapi.json') self.fakeapi_json = CachedLoad('test/namespace_fakeapi.json')
self.model.AddNamespace(self.fakeapi_json[0], self.model.AddNamespace(self.fakeapi_json[0],
'path/to/namespace_fakeapi.json') 'path/to/namespace_fakeapi.json')
self.fakeapi = self.model.namespaces.get('fakeapi') self.fakeapi = self.model.namespaces.get('fakeapi')
self.function_platforms_idl = Load('test/function_platforms.idl') self.function_platforms_idl = Load('test/function_platforms.idl')
self.model.AddNamespace(self.function_platforms_idl[0], self.model.AddNamespace(self.function_platforms_idl[0],
'/path/to/function_platforms.idl') '/path/to/function_platforms.idl')
self.function_platforms = self.model.namespaces.get('function_platforms') self.function_platforms = self.model.namespaces.get('function_platforms')
self.function_platform_win_linux_json = CachedLoad( self.function_platform_win_linux_json = CachedLoad(
'test/function_platform_win_linux.json') 'test/function_platform_win_linux.json')
self.model.AddNamespace(self.function_platform_win_linux_json[0], self.model.AddNamespace(self.function_platform_win_linux_json[0],
'path/to/function_platform_win_linux.json') 'path/to/function_platform_win_linux.json')
self.function_platform_win_linux = self.model.namespaces.get( self.function_platform_win_linux = self.model.namespaces.get(
'function_platform_win_linux') 'function_platform_win_linux')
@@ -75,7 +74,7 @@ class ModelTest(unittest.TestCase):
def testHasFunctions(self): def testHasFunctions(self):
self.assertEqual(["contains", "getAll", "remove", "request"], self.assertEqual(["contains", "getAll", "remove", "request"],
sorted(self.permissions.functions.keys())) sorted(self.permissions.functions.keys()))
def testHasTypes(self): def testHasTypes(self):
self.assertEqual(['Tab'], list(self.tabs.types.keys())) self.assertEqual(['Tab'], list(self.tabs.types.keys()))
@@ -83,41 +82,38 @@ class ModelTest(unittest.TestCase):
self.assertEqual(['Window'], list(self.windows.types.keys())) self.assertEqual(['Window'], list(self.windows.types.keys()))
def testHasProperties(self): def testHasProperties(self):
self.assertEqual(["active", "favIconUrl", "highlighted", "id", self.assertEqual([
"incognito", "index", "pinned", "selected", "status", "title", "url", "active", "favIconUrl", "highlighted", "id", "incognito", "index",
"windowId"], "pinned", "selected", "status", "title", "url", "windowId"
sorted(self.tabs.types['Tab'].properties.keys())) ], sorted(self.tabs.types['Tab'].properties.keys()))
def testProperties(self): def testProperties(self):
string_prop = self.tabs.types['Tab'].properties['status'] string_prop = self.tabs.types['Tab'].properties['status']
self.assertEqual(model.PropertyType.STRING, self.assertEqual(model.PropertyType.STRING, string_prop.type_.property_type)
string_prop.type_.property_type)
integer_prop = self.tabs.types['Tab'].properties['id'] integer_prop = self.tabs.types['Tab'].properties['id']
self.assertEqual(model.PropertyType.INTEGER, self.assertEqual(model.PropertyType.INTEGER,
integer_prop.type_.property_type) integer_prop.type_.property_type)
array_prop = self.windows.types['Window'].properties['tabs'] array_prop = self.windows.types['Window'].properties['tabs']
self.assertEqual(model.PropertyType.ARRAY, self.assertEqual(model.PropertyType.ARRAY, array_prop.type_.property_type)
array_prop.type_.property_type)
self.assertEqual(model.PropertyType.REF, self.assertEqual(model.PropertyType.REF,
array_prop.type_.item_type.property_type) array_prop.type_.item_type.property_type)
self.assertEqual('tabs.Tab', array_prop.type_.item_type.ref_type) self.assertEqual('tabs.Tab', array_prop.type_.item_type.ref_type)
object_prop = self.tabs.functions['query'].params[0] object_prop = self.tabs.functions['query'].params[0]
self.assertEqual(model.PropertyType.OBJECT, self.assertEqual(model.PropertyType.OBJECT, object_prop.type_.property_type)
object_prop.type_.property_type) self.assertEqual([
self.assertEqual( "active", "highlighted", "pinned", "status", "title", "url", "windowId",
["active", "highlighted", "pinned", "status", "title", "url", "windowType"
"windowId", "windowType"], ], sorted(object_prop.type_.properties.keys()))
sorted(object_prop.type_.properties.keys()))
def testChoices(self): def testChoices(self):
self.assertEqual(model.PropertyType.CHOICES, self.assertEqual(model.PropertyType.CHOICES,
self.tabs.functions['move'].params[0].type_.property_type) self.tabs.functions['move'].params[0].type_.property_type)
def testPropertyNotImplemented(self): def testPropertyNotImplemented(self):
(self.permissions_json[0]['types'][0] (self.permissions_json[0]['types'][0]['properties']['permissions']['type']
['properties']['permissions']['type']) = 'something' ) = 'something'
self.assertRaises(model.ParseException, self.model.AddNamespace, self.assertRaises(model.ParseException, self.model.AddNamespace,
self.permissions_json[0], 'path/to/something.json') self.permissions_json[0], 'path/to/something.json')
def testDefaultSpecifiedRedundantly(self): def testDefaultSpecifiedRedundantly(self):
test_json = CachedLoad('test/redundant_default_attribute.json') test_json = CachedLoad('test/redundant_default_attribute.json')
@@ -127,20 +123,16 @@ class ModelTest(unittest.TestCase):
' in path/to/redundant_default_attribute.json\n' ' in path/to/redundant_default_attribute.json\n'
'The attribute "optional" is specified as "False", but this is the ' 'The attribute "optional" is specified as "False", but this is the '
'default value if the attribute is not included\. It should be ' 'default value if the attribute is not included\. It should be '
'removed\.', 'removed\.', self.model.AddNamespace, test_json[0],
self.model.AddNamespace,
test_json[0],
'path/to/redundant_default_attribute.json') 'path/to/redundant_default_attribute.json')
def testReturnsAsyncMissingParametersKey(self): def testReturnsAsyncMissingParametersKey(self):
test_json = CachedLoad('test/returns_async_missing_parameters_key.json') test_json = CachedLoad('test/returns_async_missing_parameters_key.json')
self.assertRaisesRegex( self.assertRaisesRegex(
ValueError, ValueError, 'parameters key not specified on returns_async: '
'parameters key not specified on returns_async: '
'returnsAsyncMissingParametersKey.asyncNoParametersKey in ' 'returnsAsyncMissingParametersKey.asyncNoParametersKey in '
'path/to/returns_async_missing_parameters_key.json', 'path/to/returns_async_missing_parameters_key.json',
self.model.AddNamespace, self.model.AddNamespace, test_json[0],
test_json[0],
'path/to/returns_async_missing_parameters_key.json') 'path/to/returns_async_missing_parameters_key.json')
def testDescription(self): def testDescription(self):
@@ -168,45 +160,44 @@ class ModelTest(unittest.TestCase):
def testUnixName(self): def testUnixName(self):
expectations = { expectations = {
'foo': 'foo', 'foo': 'foo',
'fooBar': 'foo_bar', 'fooBar': 'foo_bar',
'fooBarBaz': 'foo_bar_baz', 'fooBarBaz': 'foo_bar_baz',
'fooBARBaz': 'foo_bar_baz', 'fooBARBaz': 'foo_bar_baz',
'fooBAR': 'foo_bar', 'fooBAR': 'foo_bar',
'FOO': 'foo', 'FOO': 'foo',
'FOOBar': 'foo_bar', 'FOOBar': 'foo_bar',
'foo.bar': 'foo_bar', 'foo.bar': 'foo_bar',
'foo.BAR': 'foo_bar', 'foo.BAR': 'foo_bar',
'foo.barBAZ': 'foo_bar_baz', 'foo.barBAZ': 'foo_bar_baz',
'foo_Bar_Baz_box': 'foo_bar_baz_box', 'foo_Bar_Baz_box': 'foo_bar_baz_box',
} }
for name in expectations: for name in expectations:
self.assertEqual(expectations[name], model.UnixName(name)) self.assertEqual(expectations[name], model.UnixName(name))
def testCamelName(self): def testCamelName(self):
expectations = { expectations = {
'foo': 'foo', 'foo': 'foo',
'fooBar': 'fooBar', 'fooBar': 'fooBar',
'foo_bar_baz': 'fooBarBaz', 'foo_bar_baz': 'fooBarBaz',
'FOO_BAR': 'FOOBar', 'FOO_BAR': 'FOOBar',
'FOO_bar': 'FOOBar', 'FOO_bar': 'FOOBar',
'_bar': 'Bar', '_bar': 'Bar',
'_bar_baz': 'BarBaz', '_bar_baz': 'BarBaz',
'bar_': 'bar', 'bar_': 'bar',
'bar_baz_': 'barBaz', 'bar_baz_': 'barBaz',
} }
for testcase, expected in expectations.items(): for testcase, expected in expectations.items():
self.assertEqual(expected, model.CamelName(testcase)) self.assertEqual(expected, model.CamelName(testcase))
def testPlatforms(self): def testPlatforms(self):
self.assertEqual([Platforms.CHROMEOS], self.assertEqual([Platforms.CHROMEOS],
self.idl_namespace_chromeos.platforms) self.idl_namespace_chromeos.platforms)
self.assertEqual( self.assertEqual([
[Platforms.CHROMEOS, Platforms.FUCHSIA, Platforms.LINUX, Platforms.MAC, Platforms.CHROMEOS, Platforms.FUCHSIA, Platforms.LINUX, Platforms.MAC,
Platforms.WIN], Platforms.WIN
self.idl_namespace_all_platforms.platforms) ], self.idl_namespace_all_platforms.platforms)
self.assertEqual(None, self.assertEqual(None, self.idl_namespace_non_specific_platforms.platforms)
self.idl_namespace_non_specific_platforms.platforms)
def testInvalidNamespacePlatform(self): def testInvalidNamespacePlatform(self):
invalid_namespace_platform = Load('test/invalid_platform_namespace.idl') invalid_namespace_platform = Load('test/invalid_platform_namespace.idl')
@@ -225,7 +216,7 @@ class ModelTest(unittest.TestCase):
def testPlatformsOnFunctionsIDL(self): def testPlatformsOnFunctionsIDL(self):
function_win_linux = self.function_platforms.functions['function_win_linux'] function_win_linux = self.function_platforms.functions['function_win_linux']
self.assertEqual([Platforms.WIN, Platforms.LINUX], self.assertEqual([Platforms.WIN, Platforms.LINUX],
function_win_linux.platforms) function_win_linux.platforms)
function_all = self.function_platforms.functions['function_all'] function_all = self.function_platforms.functions['function_all']
self.assertIsNone(function_all.platforms) self.assertIsNone(function_all.platforms)
@@ -269,5 +260,6 @@ class ModelTest(unittest.TestCase):
self.assertIn('Enum value cannot be an empty string', self.assertIn('Enum value cannot be an empty string',
str(context.exception)) str(context.exception))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@@ -18,7 +18,7 @@ def _GenerateFilenames(full_namespace):
# 4. sub_name_space.idl, # 4. sub_name_space.idl,
# 5. etc. # 5. etc.
sub_namespaces = full_namespace.split('.') sub_namespaces = full_namespace.split('.')
filenames = [ ] filenames = []
basename = None basename = None
for namespace in reversed(sub_namespaces): for namespace in reversed(sub_namespaces):
if basename is not None: if basename is not None:
@@ -39,6 +39,7 @@ class NamespaceResolver(object):
used when searching for types. used when searching for types.
- |cpp_namespace_pattern| Default namespace pattern - |cpp_namespace_pattern| Default namespace pattern
''' '''
def __init__(self, root, path, include_rules, cpp_namespace_pattern): def __init__(self, root, path, include_rules, cpp_namespace_pattern):
self._root = root self._root = root
self._include_rules = [(path, cpp_namespace_pattern)] + include_rules self._include_rules = [(path, cpp_namespace_pattern)] + include_rules
@@ -53,13 +54,12 @@ class NamespaceResolver(object):
if cpp_namespace: if cpp_namespace:
cpp_namespace_environment = CppNamespaceEnvironment(cpp_namespace) cpp_namespace_environment = CppNamespaceEnvironment(cpp_namespace)
for filename in reversed(filenames): for filename in reversed(filenames):
filepath = os.path.join(path, filename); filepath = os.path.join(path, filename)
if os.path.exists(os.path.join(self._root, filepath)): if os.path.exists(os.path.join(self._root, filepath)):
schema = SchemaLoader(self._root).LoadSchema(filepath)[0] schema = SchemaLoader(self._root).LoadSchema(filepath)[0]
return Model().AddNamespace( return Model().AddNamespace(schema,
schema, filepath,
filepath, environment=cpp_namespace_environment)
environment=cpp_namespace_environment)
return None return None
def ResolveType(self, full_name, default_namespace): def ResolveType(self, full_name, default_namespace):

@@ -17,8 +17,8 @@ import optparse
import os import os
import shlex import shlex
import urlparse import urlparse
from highlighters import ( from highlighters import (pygments_highlighter, none_highlighter,
pygments_highlighter, none_highlighter, hilite_me_highlighter) hilite_me_highlighter)
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from cpp_namespace_environment import CppNamespaceEnvironment from cpp_namespace_environment import CppNamespaceEnvironment
from namespace_resolver import NamespaceResolver from namespace_resolver import NamespaceResolver
@@ -27,6 +27,7 @@ from namespace_resolver import NamespaceResolver
class CompilerHandler(BaseHTTPRequestHandler): class CompilerHandler(BaseHTTPRequestHandler):
"""A HTTPRequestHandler that outputs the result of tools/json_schema_compiler. """A HTTPRequestHandler that outputs the result of tools/json_schema_compiler.
""" """
def do_GET(self): def do_GET(self):
parsed_url = urlparse.urlparse(self.path) parsed_url = urlparse.urlparse(self.path)
request_path = self._GetRequestPath(parsed_url) request_path = self._GetRequestPath(parsed_url)
@@ -64,38 +65,36 @@ class CompilerHandler(BaseHTTPRequestHandler):
Code panes are populated via XHR after links in the nav pane are clicked. Code panes are populated via XHR after links in the nav pane are clicked.
""" """
(head.Append('<style>') (head.Append('<style>') \
.Append('body {') .Append('body {') \
.Append(' margin: 0;') .Append(' margin: 0;') \
.Append('}') .Append('}') \
.Append('.pane {') .Append('.pane {') \
.Append(' height: 100%;') .Append(' height: 100%;') \
.Append(' overflow-x: auto;') .Append(' overflow-x: auto;') \
.Append(' overflow-y: scroll;') .Append(' overflow-y: scroll;') \
.Append(' display: inline-block;') .Append(' display: inline-block;') \
.Append('}') .Append('}') \
.Append('#nav_pane {') .Append('#nav_pane {') \
.Append(' width: 20%;') .Append(' width: 20%;') \
.Append('}') .Append('}') \
.Append('#nav_pane ul {') .Append('#nav_pane ul {') \
.Append(' list-style-type: none;') .Append(' list-style-type: none;') \
.Append(' padding: 0 0 0 1em;') .Append(' padding: 0 0 0 1em;') \
.Append('}') .Append('}') \
.Append('#cc_pane {') .Append('#cc_pane {') \
.Append(' width: 40%;') .Append(' width: 40%;') \
.Append('}') .Append('}') \
.Append('#h_pane {') .Append('#h_pane {') \
.Append(' width: 40%;') .Append(' width: 40%;') \
.Append('}') .Append('}') \
.Append('</style>') .Append('</style>')
) )
body.Append( body.Append('<div class="pane" id="nav_pane">%s</div>'
'<div class="pane" id="nav_pane">%s</div>' '<div class="pane" id="h_pane"></div>'
'<div class="pane" id="h_pane"></div>' '<div class="pane" id="cc_pane"></div>' %
'<div class="pane" id="cc_pane"></div>' % self._RenderNavPane(parsed_url.path[1:]))
self._RenderNavPane(parsed_url.path[1:])
)
# The Javascript that interacts with the nav pane and panes to show the # The Javascript that interacts with the nav pane and panes to show the
# compiled files as the URL or highlighting options change. # compiled files as the URL or highlighting options change.
@@ -187,41 +186,39 @@ updateEverything();
(file_root, file_ext) = os.path.splitext(request_path) (file_root, file_ext) = os.path.splitext(request_path)
(filedir, filename) = os.path.split(file_root) (filedir, filename) = os.path.split(file_root)
namespace_resolver = NamespaceResolver("./", namespace_resolver = NamespaceResolver("./", filedir,
filedir,
self.server.include_rules, self.server.include_rules,
self.server.cpp_namespace_pattern) self.server.cpp_namespace_pattern)
try: try:
# Get main file. # Get main file.
namespace = namespace_resolver.ResolveNamespace(filename) namespace = namespace_resolver.ResolveNamespace(filename)
type_generator = cpp_type_generator.CppTypeGenerator( type_generator = cpp_type_generator.CppTypeGenerator(
api_model, api_model, namespace_resolver, namespace)
namespace_resolver,
namespace)
# Generate code # Generate code
if file_ext == '.h': if file_ext == '.h':
cpp_code = (h_generator.HGenerator(type_generator) cpp_code = (
.Generate(namespace).Render()) h_generator.HGenerator(type_generator).Generate(namespace).Render())
elif file_ext == '.cc': elif file_ext == '.cc':
cpp_code = (cc_generator.CCGenerator(type_generator) cpp_code = (cc_generator.CCGenerator(type_generator).Generate(
.Generate(namespace).Render()) namespace).Render())
else: else:
self.send_error(404, "File not found: %s" % request_path) self.send_error(404, "File not found: %s" % request_path)
return return
# Do highlighting on the generated code # Do highlighting on the generated code
(highlighter_param, style_param) = self._GetHighlighterParams(parsed_url) (highlighter_param, style_param) = self._GetHighlighterParams(parsed_url)
head.Append('<style>' + head.Append(
'<style>' +
self.server.highlighters[highlighter_param].GetCSS(style_param) + self.server.highlighters[highlighter_param].GetCSS(style_param) +
'</style>') '</style>')
body.Append(self.server.highlighters[highlighter_param] body.Append(self.server.highlighters[highlighter_param].GetCodeElement(
.GetCodeElement(cpp_code, style_param)) cpp_code, style_param))
except IOError: except IOError:
self.send_error(404, "File not found: %s" % request_path) self.send_error(404, "File not found: %s" % request_path)
return return
except (TypeError, KeyError, AttributeError, except (TypeError, KeyError, AttributeError, AssertionError,
AssertionError, NotImplementedError) as error: NotImplementedError) as error:
body.Append('<pre>') body.Append('<pre>')
body.Append('compiler error: %s' % error) body.Append('compiler error: %s' % error)
body.Append('Check server log for more details') body.Append('Check server log for more details')
@@ -233,7 +230,7 @@ updateEverything();
""" """
query_dict = urlparse.parse_qs(parsed_url.query) query_dict = urlparse.parse_qs(parsed_url.query)
return (query_dict.get('highlighter', ['pygments'])[0], return (query_dict.get('highlighter', ['pygments'])[0],
query_dict.get('style', ['colorful'])[0]) query_dict.get('style', ['colorful'])[0])
def _RenderNavPane(self, path): def _RenderNavPane(self, path):
"""Renders an HTML nav pane. """Renders an HTML nav pane.
@@ -248,7 +245,7 @@ updateEverything();
html.Append('<select id="highlighters" onChange="updateEverything()">') html.Append('<select id="highlighters" onChange="updateEverything()">')
for name, highlighter in self.server.highlighters.items(): for name, highlighter in self.server.highlighters.items():
html.Append('<option value="%s">%s</option>' % html.Append('<option value="%s">%s</option>' %
(name, highlighter.DisplayName())) (name, highlighter.DisplayName()))
html.Append('</select>') html.Append('</select>')
html.Append('<br/>') html.Append('<br/>')
@@ -289,8 +286,7 @@ updateEverything();
html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename)) html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename))
elif file_ext in ['.json', '.idl']: elif file_ext in ['.json', '.idl']:
# cc/h panes will automatically update via the hash change event. # cc/h panes will automatically update via the hash change event.
html.Append('<li><a href="#%s">%s</a>' % html.Append('<li><a href="#%s">%s</a>' % (filename, filename))
(filename, filename))
html.Append('</ul>') html.Append('</ul>')
@@ -298,11 +294,8 @@ updateEverything();
class PreviewHTTPServer(HTTPServer, object): class PreviewHTTPServer(HTTPServer, object):
def __init__(self,
server_address, def __init__(self, server_address, handler, highlighters, include_rules,
handler,
highlighters,
include_rules,
cpp_namespace_pattern): cpp_namespace_pattern):
super(PreviewHTTPServer, self).__init__(server_address, handler) super(PreviewHTTPServer, self).__init__(server_address, handler)
self.highlighters = highlighters self.highlighters = highlighters
@@ -314,11 +307,18 @@ if __name__ == '__main__':
parser = optparse.OptionParser( parser = optparse.OptionParser(
description='Runs a server to preview the json_schema_compiler output.', description='Runs a server to preview the json_schema_compiler output.',
usage='usage: %prog [option]...') usage='usage: %prog [option]...')
parser.add_option('-p', '--port', default='8000', parser.add_option('-p',
help='port to run the server on') '--port',
parser.add_option('-n', '--namespace', default='generated_api_schemas', default='8000',
help='port to run the server on')
parser.add_option(
'-n',
'--namespace',
default='generated_api_schemas',
help='C++ namespace for generated files. e.g extensions::api.') help='C++ namespace for generated files. e.g extensions::api.')
parser.add_option('-I', '--include-rules', parser.add_option(
'-I',
'--include-rules',
help='A list of paths to include when searching for referenced objects,' help='A list of paths to include when searching for referenced objects,'
' with the namespace separated by a \':\'. Example: ' ' with the namespace separated by a \':\'. Example: '
'/foo/bar:Foo::Bar::%(namespace)s') '/foo/bar:Foo::Bar::%(namespace)s')
@@ -344,19 +344,16 @@ if __name__ == '__main__':
print('') print('')
highlighters = { highlighters = {
'hilite': hilite_me_highlighter.HiliteMeHighlighter(), 'hilite': hilite_me_highlighter.HiliteMeHighlighter(),
'none': none_highlighter.NoneHighlighter() 'none': none_highlighter.NoneHighlighter()
} }
try: try:
highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter() highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter()
except ImportError as e: except ImportError as e:
pass pass
server = PreviewHTTPServer(('', int(opts.port)), server = PreviewHTTPServer(('', int(opts.port)), CompilerHandler,
CompilerHandler, highlighters, include_rules, opts.namespace)
highlighters,
include_rules,
opts.namespace)
server.serve_forever() server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
server.socket.close() server.socket.close()

@@ -8,10 +8,12 @@ import sys
import idl_schema import idl_schema
import json_schema import json_schema
class SchemaLoader(object): class SchemaLoader(object):
'''Loads a schema from a provided filename. '''Loads a schema from a provided filename.
|root|: path to the root directory. |root|: path to the root directory.
''' '''
def __init__(self, root): def __init__(self, root):
self._root = root self._root = root

@@ -4,6 +4,7 @@
"""Utilies for the processing of schema python structures. """Utilies for the processing of schema python structures.
""" """
def CapitalizeFirstLetter(value): def CapitalizeFirstLetter(value):
return value[0].capitalize() + value[1:] return value[0].capitalize() + value[1:]

@@ -7,7 +7,9 @@ from schema_util import JsFunctionNameToClassName
from schema_util import StripNamespace from schema_util import StripNamespace
import unittest import unittest
class SchemaUtilTest(unittest.TestCase): class SchemaUtilTest(unittest.TestCase):
def testStripNamespace(self): def testStripNamespace(self):
self.assertEqual('Bar', StripNamespace('foo.Bar')) self.assertEqual('Bar', StripNamespace('foo.Bar'))
self.assertEqual('Baz', StripNamespace('Baz')) self.assertEqual('Baz', StripNamespace('Baz'))
@@ -15,11 +17,11 @@ class SchemaUtilTest(unittest.TestCase):
def testJsFunctionNameToClassName(self): def testJsFunctionNameToClassName(self):
self.assertEqual('FooBar', JsFunctionNameToClassName('foo', 'bar')) self.assertEqual('FooBar', JsFunctionNameToClassName('foo', 'bar'))
self.assertEqual('FooBar', self.assertEqual('FooBar',
JsFunctionNameToClassName('experimental.foo', 'bar')) JsFunctionNameToClassName('experimental.foo', 'bar'))
self.assertEqual('FooBarBaz', JsFunctionNameToClassName('foo.bar', 'baz'))
self.assertEqual('FooBarBaz', self.assertEqual('FooBarBaz',
JsFunctionNameToClassName('foo.bar', 'baz')) JsFunctionNameToClassName('experimental.foo.bar', 'baz'))
self.assertEqual('FooBarBaz',
JsFunctionNameToClassName('experimental.foo.bar', 'baz'))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@@ -1,7 +1,6 @@
# Copyright 2023 The Chromium Authors # Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Generator that produces a definition file for typescript. """Generator that produces a definition file for typescript.
Note: This is a work in progress, and generated definitions may need tweaking. Note: This is a work in progress, and generated definitions may need tweaking.
@@ -19,7 +18,6 @@ from js_util import JsUtil
from model import * from model import *
from schema_util import * from schema_util import *
CHROMIUM_SRC = os.path.abspath( CHROMIUM_SRC = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..")) os.path.join(os.path.dirname(__file__), "..", ".."))
@@ -59,8 +57,7 @@ class _Generator(object):
# If events are needed, add the import. # If events are needed, add the import.
if self._events_required: if self._events_required:
main_code.Substitute( main_code.Substitute(
{"imports": "import {ChromeEvent} from './chrome_event.js';"} {"imports": "import {ChromeEvent} from './chrome_event.js';"})
)
else: else:
main_code.Substitute({"imports": ""}) main_code.Substitute({"imports": ""})
main_code = self._ClangFormat(main_code) main_code = self._ClangFormat(main_code)
@@ -111,10 +108,8 @@ class _Generator(object):
type_name = self._ExtractType(prop.type_) type_name = self._ExtractType(prop.type_)
# If the ref type has additional properties, do a namespace merge. # If the ref type has additional properties, do a namespace merge.
prop_type: Type = prop.type_ prop_type: Type = prop.type_
if ( if (len(prop_type.properties) > 0
len(prop_type.properties) > 0 and prop_type.property_type == PropertyType.REF):
and prop_type.property_type == PropertyType.REF
):
type_name = self._AppendInterfaceForProperty(c, prop, type_name) type_name = self._AppendInterfaceForProperty(c, prop, type_name)
c.Append(f"export const {prop.name}: {type_name};") c.Append(f"export const {prop.name}: {type_name};")
c.Append() c.Append()
@@ -143,9 +138,8 @@ class _Generator(object):
# This appends an local only interface to allow for additional # This appends an local only interface to allow for additional
# properties on an already defined type. # properties on an already defined type.
def _AppendInterfaceForProperty( def _AppendInterfaceForProperty(self, c: Code, prop: Property,
self, c: Code, prop: Property, prop_type_name prop_type_name):
):
if prop.deprecated: if prop.deprecated:
return return
prop_type = prop.type_ prop_type = prop.type_
@@ -177,16 +171,15 @@ class _Generator(object):
# Type alias # Type alias
c.Append(f"export type {type.name} = {type.property_type.name};") c.Append(f"export type {type.name} = {type.property_type.name};")
c.Append() c.Append()
elif (type.property_type is PropertyType.ARRAY or elif (type.property_type is PropertyType.ARRAY
type.property_type is PropertyType.CHOICES) : or type.property_type is PropertyType.CHOICES):
ts_type = self._ExtractType(type) ts_type = self._ExtractType(type)
c.Append(f"export type {type.name} = {ts_type};") c.Append(f"export type {type.name} = {ts_type};")
c.Append() c.Append()
else: else:
# Adding this for things we may not have accounted for here. # Adding this for things we may not have accounted for here.
c.Append( c.Append(
f"// TODO({os.getlogin()}) -- {type.name}: {type.property_type.name}" f"// TODO({os.getlogin()}) -- {type.name}: {type.property_type.name}")
)
def _AppendInterface(self, c: Code, interface: Type): def _AppendInterface(self, c: Code, interface: Type):
c.Sblock(f"export interface {interface.name} {{") c.Sblock(f"export interface {interface.name} {{")
@@ -231,8 +224,8 @@ class _Generator(object):
ret_type = "void" ret_type = "void"
if func.returns is not None: if func.returns is not None:
ret_type = self._ExtractType(func.returns) ret_type = self._ExtractType(func.returns)
elif (func.returns_async is not None and elif (func.returns_async is not None
func.returns_async.can_return_promise): and func.returns_async.can_return_promise):
ret_type = f"Promise<{self._ExtractPromiseType(func.returns_async)}>" ret_type = f"Promise<{self._ExtractPromiseType(func.returns_async)}>"
return ret_type return ret_type
@@ -328,9 +321,8 @@ class _Generator(object):
# When the return async isn't a promise, we append it as a return callback # When the return async isn't a promise, we append it as a return callback
# at the end of the parameters. # at the end of the parameters.
use_callback = ( use_callback = (func.returns_async
func.returns_async and not func.returns_async.can_return_promise and not func.returns_async.can_return_promise)
)
if use_callback: if use_callback:
callback_params = self._ExtractParams(func.returns_async.params) callback_params = self._ExtractParams(func.returns_async.params)
if param_str: if param_str:
@@ -378,24 +370,21 @@ class _Generator(object):
def _ClangFormat(self, c: Code, level=0): def _ClangFormat(self, c: Code, level=0):
# temp = tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".js") # temp = tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".js")
# f_name = temp.name # f_name = temp.name
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile("w",
"w", encoding="utf-8", suffix=".js", delete=False encoding="utf-8",
) as f: suffix=".js",
delete=False) as f:
f.write(c.Render()) f.write(c.Render())
f_name = f.name f_name = f.name
script_path = self._GetChromiumClangFormatScriptPath() script_path = self._GetChromiumClangFormatScriptPath()
style_path = self._GetChromiumClangFormatStylePath() style_path = self._GetChromiumClangFormatStylePath()
cmd = ( cmd = (f'python3 {script_path} --fallback-style=none '
f'python3 {script_path} --fallback-style=none ' f'--style=file:{style_path} "{f_name}"')
f'--style=file:{style_path} "{f_name}"' p = subprocess.Popen(cmd,
) cwd=CHROMIUM_SRC,
p = subprocess.Popen( encoding="utf-8",
cmd, shell=True,
cwd=CHROMIUM_SRC, stdout=subprocess.PIPE)
encoding="utf-8",
shell=True,
stdout=subprocess.PIPE
)
out = p.communicate()[0] out = p.communicate()[0]
out_code = Code() out_code = Code()
out_code.Append(out) out_code.Append(out)

@@ -17,11 +17,8 @@ class TsDefinitionGeneratorTest(unittest.TestCase):
def _GetNamespace(self, fake_content, filename): def _GetNamespace(self, fake_content, filename):
"""Returns a namespace object for the given content""" """Returns a namespace object for the given content"""
is_idl = filename.endswith('.idl') is_idl = filename.endswith('.idl')
api_def = ( api_def = (idl_schema.Process(fake_content, filename)
idl_schema.Process(fake_content, filename) if is_idl else json_parse.Parse(fake_content))
if is_idl
else json_parse.Parse(fake_content)
)
m = model.Model() m = model.Model()
return m.AddNamespace(api_def[0], filename) return m.AddNamespace(api_def[0], filename)

@@ -9,14 +9,15 @@ class UtilCCHelper(object):
"""A util class that generates code that uses """A util class that generates code that uses
tools/json_schema_compiler/util.cc. tools/json_schema_compiler/util.cc.
""" """
def __init__(self, type_manager): def __init__(self, type_manager):
self._type_manager = type_manager self._type_manager = type_manager
def PopulateArrayFromListFunction(self, optional): def PopulateArrayFromListFunction(self, optional):
"""Returns the function to turn a list into a vector. """Returns the function to turn a list into a vector.
""" """
populate_list_fn = ('PopulateOptionalArrayFromList' if optional populate_list_fn = ('PopulateOptionalArrayFromList'
else 'PopulateArrayFromList') if optional else 'PopulateArrayFromList')
return ('%s::%s') % (_API_UTIL_NAMESPACE, populate_list_fn) return ('%s::%s') % (_API_UTIL_NAMESPACE, populate_list_fn)
def CreateValueFromArray(self, src): def CreateValueFromArray(self, src):
@@ -29,8 +30,8 @@ class UtilCCHelper(object):
def AppendToContainer(self, container, value): def AppendToContainer(self, container, value):
"""Appends |value| to |container|. """Appends |value| to |container|.
""" """
return '%s::AppendToContainer(%s, %s);' % ( return '%s::AppendToContainer(%s, %s);' % (_API_UTIL_NAMESPACE, container,
_API_UTIL_NAMESPACE, container, value) value)
def GetIncludePath(self): def GetIncludePath(self):
return '#include "tools/json_schema_compiler/util.h"' return '#include "tools/json_schema_compiler/util.h"'

@@ -21,9 +21,8 @@ from json_parse import OrderedDict
# idl_parser expects to be able to import certain files in its directory, # idl_parser expects to be able to import certain files in its directory,
# so let's set things up the way it wants. # so let's set things up the way it wants.
_idl_generators_path = os.path.join( _idl_generators_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, 'tools' os.pardir, os.pardir, 'tools')
)
if _idl_generators_path in sys.path: if _idl_generators_path in sys.path:
from idl_parser import idl_parser, idl_lexer, idl_node from idl_parser import idl_parser, idl_lexer, idl_node
else: else:
@@ -40,8 +39,7 @@ class SchemaCompilerError(Exception):
def __init__(self, message: str, node: IDLNode): def __init__(self, message: str, node: IDLNode):
super().__init__( super().__init__(
node.GetLogLine(f'Error processing node {node}: {message}') node.GetLogLine(f'Error processing node {node}: {message}'))
)
def GetChildWithName(node: IDLNode, name: str) -> Optional[IDLNode]: def GetChildWithName(node: IDLNode, name: str) -> Optional[IDLNode]:
@@ -56,8 +54,7 @@ def GetChildWithName(node: IDLNode, name: str) -> Optional[IDLNode]:
name was not found. name was not found.
""" """
return next( return next(
(child for child in node.GetChildren() if child.GetName() == name), None (child for child in node.GetChildren() if child.GetName() == name), None)
)
def GetTypeName(node: IDLNode) -> str: def GetTypeName(node: IDLNode) -> str:
@@ -76,8 +73,7 @@ def GetTypeName(node: IDLNode) -> str:
if child_node.GetClass() == 'Type': if child_node.GetClass() == 'Type':
return child_node.GetOneOf('Typeref').GetName() return child_node.GetOneOf('Typeref').GetName()
raise SchemaCompilerError( raise SchemaCompilerError(
'Could not find Type node when looking for Typeref name.', node 'Could not find Type node when looking for Typeref name.', node)
)
def GetExtendedAttributes(node: IDLNode) -> Optional[List[IDLNode]]: def GetExtendedAttributes(node: IDLNode) -> Optional[List[IDLNode]]:
@@ -111,9 +107,8 @@ class Type:
def __init__(self, node: IDLNode, additional_properties: dict) -> None: def __init__(self, node: IDLNode, additional_properties: dict) -> None:
assert node.GetClass() == 'Type', node.GetLogLine( assert node.GetClass() == 'Type', node.GetLogLine(
'Attempted to process a "Type" node, but was passed a "%s" node.' 'Attempted to process a "Type" node, but was passed a "%s" node.' %
% (node.GetClass()) (node.GetClass()))
)
self.node = node self.node = node
self.additional_properties = additional_properties self.additional_properties = additional_properties
@@ -136,13 +131,11 @@ class Type:
properties['type'] = 'string' properties['type'] = 'string'
else: else:
raise SchemaCompilerError( raise SchemaCompilerError(
'Unsupported basic type found when processing type.', basic_type 'Unsupported basic type found when processing type.', basic_type)
)
else: else:
unknown_child = self.node.GetChildren()[0] unknown_child = self.node.GetChildren()[0]
raise SchemaCompilerError( raise SchemaCompilerError('Unsupported type class when processing type.',
'Unsupported type class when processing type.', unknown_child unknown_child)
)
return properties return properties
@@ -241,8 +234,7 @@ class IDLSchema:
browser_node = GetChildWithName(self.idl, 'Browser') browser_node = GetChildWithName(self.idl, 'Browser')
if browser_node is None or browser_node.GetClass() != 'Interface': if browser_node is None or browser_node.GetClass() != 'Interface':
raise SchemaCompilerError( raise SchemaCompilerError(
'Required partial Browser interface not found in schema.', self.idl 'Required partial Browser interface not found in schema.', self.idl)
)
# The 'Browser' Interface has one attribute describing the name this API is # The 'Browser' Interface has one attribute describing the name this API is
# exposed on. # exposed on.
@@ -298,9 +290,8 @@ def Main():
for i, char in enumerate(contents): for i, char in enumerate(contents):
if not char.isascii(): if not char.isascii():
raise Exception( raise Exception(
'Non-ascii character "%s" (ord %d) found at offset %d.' 'Non-ascii character "%s" (ord %d) found at offset %d.' %
% (char, ord(char), i) (char, ord(char), i))
)
idl = idl_parser.IDLParser().ParseData(contents, '<stdin>') idl = idl_parser.IDLParser().ParseData(contents, '<stdin>')
schema = IDLSchema(idl).process() schema = IDLSchema(idl).process()
print(json.dumps(schema, indent=2)) print(json.dumps(schema, indent=2))

@@ -36,19 +36,31 @@ class WebIdlSchemaTest(unittest.TestCase):
getReturns(schema, 'returnsVoid'), getReturns(schema, 'returnsVoid'),
) )
self.assertEqual( self.assertEqual(
{'name': 'returnsBoolean', 'type': 'boolean'}, {
'name': 'returnsBoolean',
'type': 'boolean'
},
getReturns(schema, 'returnsBoolean'), getReturns(schema, 'returnsBoolean'),
) )
self.assertEqual( self.assertEqual(
{'name': 'returnsDouble', 'type': 'number'}, {
'name': 'returnsDouble',
'type': 'number'
},
getReturns(schema, 'returnsDouble'), getReturns(schema, 'returnsDouble'),
) )
self.assertEqual( self.assertEqual(
{'name': 'returnsLong', 'type': 'integer'}, {
'name': 'returnsLong',
'type': 'integer'
},
getReturns(schema, 'returnsLong'), getReturns(schema, 'returnsLong'),
) )
self.assertEqual( self.assertEqual(
{'name': 'returnsDOMString', 'type': 'string'}, {
'name': 'returnsDOMString',
'type': 'string'
},
getReturns(schema, 'returnsDOMString'), getReturns(schema, 'returnsDOMString'),
) )
@@ -62,8 +74,7 @@ class WebIdlSchemaTest(unittest.TestCase):
def testMissingBrowserInterface(self): def testMissingBrowserInterface(self):
expected_error_regex = ( expected_error_regex = (
'.* File\(test\/web_idl\/missing_browser_interface.idl\): Required' '.* File\(test\/web_idl\/missing_browser_interface.idl\): Required'
' partial Browser interface not found in schema\.' ' partial Browser interface not found in schema\.')
)
self.assertRaisesRegex( self.assertRaisesRegex(
SchemaCompilerError, SchemaCompilerError,
expected_error_regex, expected_error_regex,
@@ -76,8 +87,7 @@ class WebIdlSchemaTest(unittest.TestCase):
def testMissingAttributeOnBrowser(self): def testMissingAttributeOnBrowser(self):
expected_error_regex = ( expected_error_regex = (
'.* Interface\(Browser\): The partial Browser interface should have' '.* Interface\(Browser\): The partial Browser interface should have'
' exactly one attribute for the name the API will be exposed under\.' ' exactly one attribute for the name the API will be exposed under\.')
)
self.assertRaisesRegex( self.assertRaisesRegex(
Exception, Exception,
expected_error_regex, expected_error_regex,
@@ -90,8 +100,7 @@ class WebIdlSchemaTest(unittest.TestCase):
def testUnsupportedBasicType(self): def testUnsupportedBasicType(self):
expected_error_regex = ( expected_error_regex = (
'.* PrimitiveType\(float\): Unsupported basic type found when' '.* PrimitiveType\(float\): Unsupported basic type found when'
' processing type\.' ' processing type\.')
)
self.assertRaisesRegex( self.assertRaisesRegex(
SchemaCompilerError, SchemaCompilerError,
expected_error_regex, expected_error_regex,
@@ -103,8 +112,7 @@ class WebIdlSchemaTest(unittest.TestCase):
# doesn't support yet throws an error. # doesn't support yet throws an error.
def testUnsupportedTypeClass(self): def testUnsupportedTypeClass(self):
expected_error_regex = ( expected_error_regex = (
'.* Any\(\): Unsupported type class when processing type\.' '.* Any\(\): Unsupported type class when processing type\.')
)
self.assertRaisesRegex( self.assertRaisesRegex(
SchemaCompilerError, SchemaCompilerError,
expected_error_regex, expected_error_regex,