Add ICU message format support
[chromium-blink-merge.git] / tools / json_schema_compiler / js_externs_generator.py
blobb76c847ac66bc523e02b5957d42453647ad7a182
1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4 """
5 Generator that produces an externs file for the Closure Compiler.
6 Note: This is a work in progress, and generated externs may require tweaking.
8 See https://developers.google.com/closure/compiler/docs/api-tutorial3#externs
9 """
11 from code import Code
12 from model import *
13 from schema_util import *
15 import os
16 from datetime import datetime
17 import re
19 LICENSE = ("""// Copyright %s The Chromium Authors. All rights reserved.
20 // Use of this source code is governed by a BSD-style license that can be
21 // found in the LICENSE file.
22 """ % datetime.now().year)
24 class JsExternsGenerator(object):
25 def Generate(self, namespace):
26 return _Generator(namespace).Generate()
28 class _Generator(object):
29 def __init__(self, namespace):
30 self._namespace = namespace
32 def Generate(self):
33 """Generates a Code object with the schema for the entire namespace.
34 """
35 c = Code()
36 (c.Append(LICENSE)
37 .Append()
38 .Append('/** @fileoverview Externs generated from namespace: %s */' %
39 self._namespace.name)
40 .Append())
42 c.Cblock(self._GenerateNamespaceObject())
44 for js_type in self._namespace.types.values():
45 c.Cblock(self._GenerateType(js_type))
47 for function in self._namespace.functions.values():
48 c.Cblock(self._GenerateFunction(function))
50 for event in self._namespace.events.values():
51 c.Cblock(self._GenerateEvent(event))
53 return c
55 def _GenerateType(self, js_type):
56 """Given a Type object, returns the Code for this type's definition.
57 """
58 c = Code()
59 if js_type.property_type is PropertyType.ENUM:
60 c.Concat(self._GenerateEnumJsDoc(js_type))
61 else:
62 c.Concat(self._GenerateTypeJsDoc(js_type))
64 return c
66 def _GenerateEnumJsDoc(self, js_type):
67 """ Given an Enum Type object, returns the Code for the enum's definition.
68 """
69 c = Code()
70 (c.Sblock(line='/**', line_prefix=' * ')
71 .Append('@enum {string}')
72 .Append(self._GenerateSeeLink('type', js_type.simple_name))
73 .Eblock(' */'))
74 c.Append('chrome.%s.%s = {' % (self._namespace.name, js_type.name))
76 def get_property_name(e):
77 # Enum properties are normified to be in ALL_CAPS_STYLE.
78 # Assume enum '1ring-rulesThemAll'.
79 # Transform to '1ring-rules_Them_All'.
80 e = re.sub(r'([a-z])([A-Z])', r'\1_\2', e)
81 # Transform to '1ring_rules_Them_All'.
82 e = re.sub(r'\W', '_', e)
83 # Transform to '_1ring_rules_Them_All'.
84 e = re.sub(r'^(\d)', r'_\1', e)
85 # Transform to '_1RING_RULES_THEM_ALL'.
86 return e.upper()
88 c.Append('\n'.join(
89 [" %s: '%s'," % (get_property_name(v.name), v.name)
90 for v in js_type.enum_values]))
91 c.Append('};')
92 return c
94 def _IsTypeConstructor(self, js_type):
95 """Returns true if the given type should be a @constructor. If this returns
96 false, the type is a typedef.
97 """
98 return any(prop.type_.property_type is PropertyType.FUNCTION
99 for prop in js_type.properties.values())
101 def _GenerateTypeJsDoc(self, js_type):
102 """Generates the documentation for a type as a Code.
104 Returns an empty code object if the object has no documentation.
106 c = Code()
107 c.Sblock(line='/**', line_prefix=' * ')
109 if js_type.description:
110 for line in js_type.description.splitlines():
111 c.Append(line)
113 is_constructor = self._IsTypeConstructor(js_type)
114 if is_constructor:
115 c.Comment('@constructor', comment_prefix = ' * ', wrap_indent=4)
116 else:
117 c.Concat(self._GenerateTypedef(js_type.properties))
119 c.Append(self._GenerateSeeLink('type', js_type.simple_name))
120 c.Eblock(' */')
122 var = 'var ' + js_type.simple_name
123 if is_constructor: var += ' = function() {}'
124 var += ';'
125 c.Append(var)
127 return c
129 def _GenerateTypedef(self, properties):
130 """Given an OrderedDict of properties, returns a Code containing a @typedef.
132 if not properties: return Code()
134 c = Code()
135 c.Append('@typedef {')
136 c.Concat(self._GenerateObjectDefinition(properties), new_line=False)
137 c.Append('}', new_line=False)
138 return c
140 def _GenerateObjectDefinition(self, properties):
141 """Given an OrderedDict of properties, returns a Code containing the
142 description of an object.
144 if not properties: return Code()
146 c = Code()
147 c.Sblock('{')
148 first = True
149 for field, prop in properties.items():
150 # Avoid trailing comma.
151 # TODO(devlin): This will be unneeded, if/when
152 # https://github.com/google/closure-compiler/issues/796 is fixed.
153 if not first:
154 c.Append(',', new_line=False)
155 first = False
156 js_type = self._TypeToJsType(prop.type_)
157 if prop.optional:
158 js_type = (Code().
159 Append('(').
160 Concat(js_type, new_line=False).
161 Append('|undefined)', new_line=False))
162 c.Append('%s: ' % field, strip_right=False)
163 c.Concat(js_type, new_line=False)
165 c.Eblock('}')
167 return c
169 def _GenerateFunctionJsDoc(self, function):
170 """Generates the documentation for a function as a Code.
172 Returns an empty code object if the object has no documentation.
174 c = Code()
175 c.Sblock(line='/**', line_prefix=' * ')
177 if function.description:
178 c.Comment(function.description, comment_prefix='')
180 def append_field(c, tag, js_type, name, optional, description):
181 c.Append('@%s {' % tag)
182 c.Concat(js_type, new_line=False)
183 if optional:
184 c.Append('=', new_line=False)
185 c.Append('} %s' % name, new_line=False)
186 if description:
187 c.Comment(' %s' % description, comment_prefix='',
188 wrap_indent=4, new_line=False)
190 for param in function.params:
191 append_field(c, 'param', self._TypeToJsType(param.type_), param.name,
192 param.optional, param.description)
194 if function.callback:
195 append_field(c, 'param', self._FunctionToJsFunction(function.callback),
196 function.callback.name, function.callback.optional,
197 function.callback.description)
199 if function.returns:
200 append_field(c, 'return', self._TypeToJsType(function.returns),
201 '', False, function.returns.description)
203 if function.deprecated:
204 c.Append('@deprecated %s' % function.deprecated)
206 c.Append(self._GenerateSeeLink('method', function.name))
208 c.Eblock(' */')
209 return c
211 def _FunctionToJsFunction(self, function):
212 """Converts a model.Function to a JS type (i.e., function([params])...)"""
213 c = Code()
214 c.Append('function(')
215 for i, param in enumerate(function.params):
216 c.Concat(self._TypeToJsType(param.type_), new_line=False)
217 if i is not len(function.params) - 1:
218 c.Append(', ', new_line=False, strip_right=False)
219 c.Append('):', new_line=False)
221 if function.returns:
222 c.Concat(self._TypeToJsType(function.returns), new_line=False)
223 else:
224 c.Append('void', new_line=False)
226 return c
228 def _TypeToJsType(self, js_type):
229 """Converts a model.Type to a JS type (number, Array, etc.)"""
230 if js_type.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE):
231 return Code().Append('number')
232 if js_type.property_type is PropertyType.OBJECT:
233 if js_type.properties:
234 return self._GenerateObjectDefinition(js_type.properties)
235 return Code().Append('Object')
236 if js_type.property_type is PropertyType.ARRAY:
237 return (Code().Append('!Array<').
238 Concat(self._TypeToJsType(js_type.item_type), new_line=False).
239 Append('>', new_line=False))
240 if js_type.property_type is PropertyType.REF:
241 ref_type = js_type.ref_type
242 # Enums are defined as chrome.fooAPI.MyEnum, but types are defined simply
243 # as MyType.
244 if self._namespace.types[ref_type].property_type is PropertyType.ENUM:
245 ref_type = '!chrome.%s.%s' % (self._namespace.name, ref_type)
246 return Code().Append(ref_type)
247 if js_type.property_type is PropertyType.CHOICES:
248 c = Code()
249 c.Append('(')
250 for i, choice in enumerate(js_type.choices):
251 c.Concat(self._TypeToJsType(choice), new_line=False)
252 if i is not len(js_type.choices) - 1:
253 c.Append('|', new_line=False)
254 c.Append(')', new_line=False)
255 return c
256 if js_type.property_type is PropertyType.FUNCTION:
257 return self._FunctionToJsFunction(js_type.function)
258 if js_type.property_type is PropertyType.ANY:
259 return Code().Append('*')
260 if js_type.property_type.is_fundamental:
261 return Code().Append(js_type.property_type.name)
262 return Code().Append('?') # TODO(tbreisacher): Make this more specific.
264 def _GenerateFunction(self, function):
265 """Generates the code representing a function, including its documentation.
266 For example:
269 * @param {string} title The new title.
271 chrome.window.setTitle = function(title) {};
273 c = Code()
274 params = self._GenerateFunctionParams(function)
275 (c.Concat(self._GenerateFunctionJsDoc(function))
276 .Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name,
277 function.name,
278 params))
280 return c
282 def _GenerateEvent(self, event):
283 """Generates the code representing an event.
284 For example:
286 /** @type {!ChromeEvent} */
287 chrome.bookmarks.onChildrenReordered;
289 c = Code()
290 c.Sblock(line='/**', line_prefix=' * ')
291 if (event.description):
292 c.Comment(event.description, comment_prefix='')
293 c.Append('@type {!ChromeEvent}')
294 c.Append(self._GenerateSeeLink('event', event.name))
295 c.Eblock(' */')
296 c.Append('chrome.%s.%s;' % (self._namespace.name, event.name))
297 return c
299 def _GenerateNamespaceObject(self):
300 """Generates the code creating namespace object.
301 For example:
304 * @const
306 chrome.bookmarks = {};
308 c = Code()
309 (c.Append("""/**
310 * @const
311 */""")
312 .Append('chrome.%s = {};' % self._namespace.name))
313 return c
315 def _GenerateFunctionParams(self, function):
316 params = function.params[:]
317 if function.callback:
318 params.append(function.callback)
319 return ', '.join(param.name for param in params)
321 def _GenerateSeeLink(self, object_type, object_name):
322 """Generates a @see link for a given API 'object' (type, method, or event).
325 # NOTE(devlin): This is kind of a hack. Some APIs will be hosted on
326 # developer.chrome.com/apps/ instead of /extensions/, and some APIs have
327 # '.'s in them (like app.window), which should resolve to 'app_window'.
328 # Luckily, the doc server has excellent url resolution, and knows exactly
329 # what we mean. This saves us from needing any complicated logic here.
330 return ('@see https://developer.chrome.com/extensions/%s#%s-%s' %
331 (self._namespace.name, object_type, object_name))