# Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from code import Code from model import PropertyType 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. """ INFO = """// This file was generated by: // %s. """ class JsUtil(object): """A helper class for generating JS Code. """ def GetLicense(self): """Returns the license text for JS extern and interface files. """ return (LICENSE % datetime.now().year) def GetInfo(self, tool): """Returns text describing how the file was generated. """ return (INFO % tool) def AppendObjectDefinition(self, c, namespace_name, properties, new_line=True): """Given an OrderedDict of properties, returns a Code containing the description of an object. """ if not properties: return c.Sblock('{', new_line=new_line) first = True for field, prop in properties.items(): # Avoid trailing comma. # TODO(devlin): This will be unneeded, if/when # https://github.com/google/closure-compiler/issues/796 is fixed. if not first: c.Append(',', new_line=False) first = False js_type = self._TypeToJsType(namespace_name, prop.type_) if prop.optional: js_type = (Code().Append('(') .Concat(js_type, new_line=False) .Append('|undefined)', new_line=False)) c.Append('%s: ' % field, strip_right=False) c.Concat(js_type, new_line=False) c.Eblock('}') def AppendFunctionJsDoc(self, c, namespace_name, function): """Appends the documentation for a function as a Code. Returns an empty code object if the object has no documentation. """ c.Sblock(line='/**', line_prefix=' * ') if function.description: c.Comment(function.description, comment_prefix='') def append_field(c, tag, js_type, name, optional, description): c.Append('@%s {' % tag) c.Concat(js_type, new_line=False) if optional: c.Append('=', new_line=False) c.Append('}', new_line=False) c.Comment(' %s' % name, comment_prefix='', wrap_indent=4, new_line=False) if description: c.Comment(' %s' % description, comment_prefix='', wrap_indent=4, new_line=False) for param in function.params: append_field(c, 'param', self._TypeToJsType(namespace_name, param.type_), param.name, param.optional, param.description) if function.callback: append_field(c, 'param', self._FunctionToJsFunction(namespace_name, function.callback), function.callback.name, function.callback.optional, function.callback.description) if function.returns: append_field(c, 'return', self._TypeToJsType(namespace_name, function.returns), '', False, function.returns.description) if function.deprecated: c.Append('@deprecated %s' % function.deprecated) c.Append(self.GetSeeLink(namespace_name, 'method', function.name)) c.Eblock(' */') def _FunctionToJsFunction(self, namespace_name, function): """Converts a model.Function to a JS type (i.e., function([params])...)""" c = Code() c.Append('function(') for i, param in enumerate(function.params): c.Concat(self._TypeToJsType(namespace_name, param.type_), new_line=False) if i is not len(function.params) - 1: c.Append(', ', new_line=False, strip_right=False) c.Append('):', new_line=False) if function.returns: c.Concat(self._TypeToJsType(namespace_name, function.returns), new_line=False) else: c.Append('void', new_line=False) return c def _TypeToJsType(self, namespace_name, js_type): """Converts a model.Type to a JS type (number, Array, etc.)""" if js_type.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE): return Code().Append('number') if js_type.property_type is PropertyType.OBJECT: if js_type.properties: c = Code() self.AppendObjectDefinition(c, namespace_name, js_type.properties) return c return Code().Append('Object') if js_type.property_type is PropertyType.ARRAY: return (Code().Append('!Array<'). Concat(self._TypeToJsType(namespace_name, js_type.item_type), new_line=False). Append('>', new_line=False)) if js_type.property_type is PropertyType.REF: ref_type = '!chrome.%s.%s' % (namespace_name, js_type.ref_type) return Code().Append(ref_type) if js_type.property_type is PropertyType.CHOICES: c = Code() c.Append('(') for i, choice in enumerate(js_type.choices): c.Concat(self._TypeToJsType(namespace_name, choice), new_line=False) if i is not len(js_type.choices) - 1: c.Append('|', new_line=False) c.Append(')', new_line=False) return c if js_type.property_type is PropertyType.FUNCTION: return self._FunctionToJsFunction(namespace_name, js_type.function) if js_type.property_type is PropertyType.BINARY: return Code().Append('ArrayBuffer') if js_type.property_type is PropertyType.ANY: return Code().Append('*') if js_type.property_type.is_fundamental: return Code().Append(js_type.property_type.name) return Code().Append('?') # TODO(tbreisacher): Make this more specific. def GetSeeLink(self, namespace_name, object_type, object_name): """Returns a @see link for a given API 'object' (type, method, or event). """ # NOTE(devlin): This is kind of a hack. Some APIs will be hosted on # developer.chrome.com/apps/ instead of /extensions/, and some APIs have # '.'s in them (like app.window), which should resolve to 'app_window'. # Luckily, the doc server has excellent url resolution, and knows exactly # what we mean. This saves us from needing any complicated logic here. return ('@see https://developer.chrome.com/extensions/%s#%s-%s' % (namespace_name, object_type, object_name))