Chromium Code Reviews| Index: tools/json_schema_compiler/js_externs_generator.py |
| diff --git a/tools/json_schema_compiler/js_externs_generator.py b/tools/json_schema_compiler/js_externs_generator.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f9a77bdb8d46cd689992d6cf3cd4180ada3fc05f |
| --- /dev/null |
| +++ b/tools/json_schema_compiler/js_externs_generator.py |
| @@ -0,0 +1,234 @@ |
| +# Copyright (c) 2013 The Chromium Authors. All rights reserved. |
|
hirono
2014/10/03 10:10:42
nit: 2014?
|
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| +""" |
| +Generator that produces an externs file for the Closure Compiler. |
| + |
| +See https://developers.google.com/closure/compiler/docs/api-tutorial3#externs |
| +""" |
| + |
| +from code import Code |
| +from model import * |
| +from schema_util import * |
| + |
| +import os |
| +from datetime import datetime |
| + |
| +LICENSE = ("""// Copyright %s The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| +""" % datetime.now().year) |
| + |
| +class JsExternsGenerator(object): |
| + def __init__(self): |
| + pass |
| + |
| + def Generate(self, namespace): |
| + return _Generator(namespace).Generate() |
| + |
| +class _Generator(object): |
| + def __init__(self, namespace): |
| + self._namespace = namespace |
| + |
| + def Generate(self): |
| + """Generates a Code object with the .dart for the entire namespace. |
| + """ |
| + c = Code() |
| + (c.Append(LICENSE) |
| + .Append() |
| + .Append('/** @fileoverview Externs generated from namespace: %s */' % |
| + self._namespace.name) |
| + .Append()) |
| + |
| + for type_ in self._namespace.types.values(): |
| + c.Cblock(self._GenerateType(type_)) |
| + |
| + c.Cblock(self._GenerateNamespaceObject()) |
| + |
| + for function in self._namespace.functions.values(): |
| + c.Cblock(self._GenerateFunction(function)) |
| + |
| + for event in self._namespace.events.values(): |
| + c.Cblock(self._GenerateEvent(event)) |
| + |
| + return c |
| + |
| + def _GenerateType(self, type_): |
| + """Given a Type object, returns the Code for this type's definition. |
| + |
| + """ |
| + c = Code() |
| + |
| + # Since enums are just treated as strings for now, don't generate their |
| + # type. |
| + if type_.property_type is PropertyType.ENUM: |
| + return c |
| + |
| + c.Concat(self._GenerateTypeJsDoc(type_)) |
| + |
| + if self._IsTypeConstructor(type_): |
| + c.Append('var ' + type_.simple_name + ' = function();') |
|
hirono
2014/10/03 10:10:42
Is it OK dropping arguments of constructor?
Vitaly Pavlenko
2014/10/03 23:33:44
You're right, that's a mistake. By the way, will y
|
| + else: |
| + c.Append('var ' + type_.simple_name + ';') |
| + |
| + return c |
| + |
| + def _IsTypeConstructor(self, type_): |
| + """Returns true if the given type should be a @constructor. If this returns |
| + false, the type is a typedef. |
| + """ |
| + return any(prop.type_.property_type is PropertyType.FUNCTION |
| + for prop in type_.properties.values()) |
| + |
| + def _GenerateTypeJsDoc(self, type_): |
| + """Generates the documentation for a type as a Code. |
| + |
| + Returns an empty code object if the object has no documentation. |
| + """ |
| + c = Code() |
| + c.Append('/**') |
| + |
| + if type_.description: |
| + for line in type_.description.split('\n'): |
| + c.Comment(line, comment_prefix=' * ') |
| + |
| + if self._IsTypeConstructor(type_): |
| + c.Comment('@constructor', comment_prefix = ' * ') |
| + else: |
| + c.Concat(self._GenerateTypedef(type_.properties)) |
| + |
| + c.Append(' */') |
| + return c |
| + |
| + def _GenerateTypedef(self, properties): |
| + """Given an OrderedDict of properties, returns a Code containing a @typedef. |
| + """ |
| + if not properties: return Code() |
| + |
| + lines = [] |
| + lines.append('@typedef {{') |
| + for field, property_ in properties.items(): |
| + js_type = self._TypeToJsType(property_.type_) |
| + if property_.optional: |
| + js_type = '(%s|undefined)' % js_type |
| + lines.append(' %s: %s,' % (field, js_type)) |
| + |
| + # remove last trailing comma |
| + lines[-1] = lines[-1][:-1] |
| + lines.append('}}') |
| + # TODO(tbreisacher): Add '@see <link to documentation>'. |
| + |
| + lines = [' * ' + line for line in lines] |
| + codeString = '\n'.join(lines) |
|
Tyler Breisacher (Chromium)
2015/02/25 00:22:05
Can probably inline this?
codeString = '\n'.join(
|
| + c = Code() |
| + c.Append(codeString) |
| + return c |
| + |
| + def _GenerateFunctionJsDoc(self, function): |
| + """Generates the documentation for a function as a Code. |
| + |
| + Returns an empty code object if the object has no documentation. |
| + """ |
| + c = Code() |
| + c.Append('/**') |
| + |
| + if function.description: |
| + for line in function.description.split('\n'): |
| + c.Comment(line, comment_prefix=' * ') |
| + |
| + for param in function.params: |
| + js_type = self._TypeToJsType(param.type_) |
| + |
| + if param.optional: |
| + js_type += '=' |
| + |
| + param_doc = '@param {%s} %s %s' % (js_type, |
| + param.name, |
| + param.description or '') |
| + c.Comment(param_doc, comment_prefix=' * ') |
| + |
| + if function.callback: |
| + # TODO(tbreisacher): Convert Function to function() for better |
| + # typechecking. |
| + js_type = 'Function' |
| + if function.callback.optional: |
| + js_type += '=' |
| + param_doc = '@param {%s} %s %s' % (js_type, |
| + function.callback.name, |
| + function.callback.description or '') |
| + c.Comment(param_doc, comment_prefix=' * ') |
| + |
| + if function.returns: |
| + return_doc = '@return {%s} %s' % (self._TypeToJsType(function.returns), |
| + function.returns.description) |
| + c.Comment(return_doc, comment_prefix=' * ') |
| + |
| + c.Append(' */') |
| + return c |
| + |
| + def _TypeToJsType(self, type_): |
| + """Converts a model.Type to a JS type (number, Array, etc.)""" |
| + if type_.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE): |
| + return 'number' |
| + elif type_.property_type is PropertyType.OBJECT: |
| + return 'Object' |
| + elif type_.property_type is PropertyType.ARRAY: |
| + return 'Array' |
| + elif type_.property_type is PropertyType.REF: |
| + return type_.ref_type |
| + elif type_.property_type.is_fundamental: |
| + return type_.property_type.name |
| + else: |
| + return '?' # TODO make this more specific |
|
Tyler Breisacher (Chromium)
2015/02/25 00:22:05
put someone's name on this TODO. I reluctantly vol
|
| + |
| + def _GenerateFunction(self, function): |
| + """Generates the code representing a function, including its documentation. |
| + For example: |
| + |
| + /** |
| + * @param {string} title The new title. |
| + */ |
| + chrome.window.setTitle = function(title) {}; |
| + """ |
| + c = Code() |
| + params = self._GenerateFunctionParams(function) |
| + (c.Concat(self._GenerateFunctionJsDoc(function)) |
| + .Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name, |
| + function.name, |
| + params)) |
| + ) |
| + return c |
| + |
| + def _GenerateEvent(self, event): |
| + """Generates the code representing an event. |
| + For example: |
| + |
| + /** @type {!ChromeEvent} */ |
| + chrome.bookmarks.onChildrenReordered; |
| + """ |
| + c = Code() |
| + (c.Append('/** @type {!ChromeEvent} */') |
| + .Append('chrome.%s.%s;' % (self._namespace.name, event.name))) |
| + return c |
| + |
| + def _GenerateNamespaceObject(self): |
| + """Generates the code creating namespace object. |
| + For example: |
| + |
| + /** |
| + * @const |
| + */ |
| + chrome.bookmarks = {}; |
| + """ |
| + c = Code() |
| + (c.Append("""/** |
| + * @const |
| + */""") |
| + .Append('chrome.%s = {};' % self._namespace.name)) |
| + return c |
| + |
| + def _GenerateFunctionParams(self, function): |
| + params = function.params[:] |
| + if function.callback: |
| + params.append(function.callback) |
| + return ', '.join(param.name for param in params) |