Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(92)

Side by Side Diff: tools/json_schema_compiler/js_util.py

Issue 1488773003: Add js_interface_generator for generating extensions interfaces (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: similarity=25 Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 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.
7
8 See https://developers.google.com/closure/compiler/docs/api-tutorial3#externs
9 """
10 4
11 from code import Code 5 from code import Code
12 from model import * 6 from model import PropertyType
13 from schema_util import *
14 7
15 import os 8 class JsUtil(object):
16 import sys 9 """A helper class for generating JS Code.
17 from datetime import datetime 10 """
18 import re 11 def GenerateObjectDefinition(self, namespace_name, properties):
19
20 LICENSE = ("""// Copyright %s The Chromium Authors. All rights reserved.
21 // Use of this source code is governed by a BSD-style license that can be
22 // found in the LICENSE file.
23 """ % datetime.now().year)
24
25 INFO = """// This file was generated by:
26 // %s.
27 // NOTE: The format of types has changed. 'FooType' is now
28 // 'chrome.%s.FooType'.
29 // Please run the closure compiler before committing changes.
30 // See https://code.google.com/p/chromium/wiki/ClosureCompilation.
31 """
32
33 class JsExternsGenerator(object):
34 def Generate(self, namespace):
35 return _Generator(namespace).Generate()
36
37 class _Generator(object):
38 def __init__(self, namespace):
39 self._namespace = namespace
40
41 def Generate(self):
42 """Generates a Code object with the schema for the entire namespace.
43 """
44 c = Code()
45 (c.Append(LICENSE)
46 .Append()
47 .Append(INFO % (sys.argv[0], self._namespace.name))
48 .Append()
49 .Append('/** @fileoverview Externs generated from namespace: %s */' %
50 self._namespace.name)
51 .Append())
52
53 c.Cblock(self._GenerateNamespaceObject())
54
55 for js_type in self._namespace.types.values():
56 c.Cblock(self._GenerateType(js_type))
57
58 for function in self._namespace.functions.values():
59 c.Cblock(self._GenerateFunction(function))
60
61 for event in self._namespace.events.values():
62 c.Cblock(self._GenerateEvent(event))
63
64 return c
65
66 def _GenerateType(self, js_type):
67 """Given a Type object, returns the Code for this type's definition.
68 """
69 c = Code()
70 if js_type.property_type is PropertyType.ENUM:
71 c.Concat(self._GenerateEnumJsDoc(js_type))
72 else:
73 c.Concat(self._GenerateTypeJsDoc(js_type))
74
75 return c
76
77 def _GenerateEnumJsDoc(self, js_type):
78 """ Given an Enum Type object, returns the Code for the enum's definition.
79 """
80 c = Code()
81 (c.Sblock(line='/**', line_prefix=' * ')
82 .Append('@enum {string}')
83 .Append(self._GenerateSeeLink('type', js_type.simple_name))
84 .Eblock(' */'))
85 c.Append('chrome.%s.%s = {' % (self._namespace.name, js_type.name))
86
87 def get_property_name(e):
88 # Enum properties are normified to be in ALL_CAPS_STYLE.
89 # Assume enum '1ring-rulesThemAll'.
90 # Transform to '1ring-rules_Them_All'.
91 e = re.sub(r'([a-z])([A-Z])', r'\1_\2', e)
92 # Transform to '1ring_rules_Them_All'.
93 e = re.sub(r'\W', '_', e)
94 # Transform to '_1ring_rules_Them_All'.
95 e = re.sub(r'^(\d)', r'_\1', e)
96 # Transform to '_1RING_RULES_THEM_ALL'.
97 return e.upper()
98
99 c.Append('\n'.join(
100 [" %s: '%s'," % (get_property_name(v.name), v.name)
101 for v in js_type.enum_values]))
102 c.Append('};')
103 return c
104
105 def _IsTypeConstructor(self, js_type):
106 """Returns true if the given type should be a @constructor. If this returns
107 false, the type is a typedef.
108 """
109 return any(prop.type_.property_type is PropertyType.FUNCTION
110 for prop in js_type.properties.values())
111
112 def _GenerateTypeJsDoc(self, js_type):
113 """Generates the documentation for a type as a Code.
114
115 Returns an empty code object if the object has no documentation.
116 """
117 c = Code()
118 c.Sblock(line='/**', line_prefix=' * ')
119
120 if js_type.description:
121 for line in js_type.description.splitlines():
122 c.Append(line)
123
124 is_constructor = self._IsTypeConstructor(js_type)
125 if is_constructor:
126 c.Comment('@constructor', comment_prefix = ' * ', wrap_indent=4)
127 else:
128 c.Concat(self._GenerateTypedef(js_type.properties))
129
130 c.Append(self._GenerateSeeLink('type', js_type.simple_name))
131 c.Eblock(' */')
132
133 var = 'chrome.%s.%s' % (js_type.namespace.name, js_type.simple_name)
134 if is_constructor: var += ' = function() {}'
135 var += ';'
136 c.Append(var)
137
138 return c
139
140 def _GenerateTypedef(self, properties):
141 """Given an OrderedDict of properties, returns a Code containing a @typedef.
142 """
143 if not properties: return Code()
144
145 c = Code()
146 c.Append('@typedef {')
147 c.Concat(self._GenerateObjectDefinition(properties), new_line=False)
148 c.Append('}', new_line=False)
149 return c
150
151 def _GenerateObjectDefinition(self, properties):
152 """Given an OrderedDict of properties, returns a Code containing the 12 """Given an OrderedDict of properties, returns a Code containing the
153 description of an object. 13 description of an object.
154 """ 14 """
155 if not properties: return Code() 15 if not properties: return Code()
156 16
157 c = Code() 17 c = Code()
158 c.Sblock('{') 18 c.Sblock('{')
159 first = True 19 first = True
160 for field, prop in properties.items(): 20 for field, prop in properties.items():
161 # Avoid trailing comma. 21 # Avoid trailing comma.
162 # TODO(devlin): This will be unneeded, if/when 22 # TODO(devlin): This will be unneeded, if/when
163 # https://github.com/google/closure-compiler/issues/796 is fixed. 23 # https://github.com/google/closure-compiler/issues/796 is fixed.
164 if not first: 24 if not first:
165 c.Append(',', new_line=False) 25 c.Append(',', new_line=False)
166 first = False 26 first = False
167 js_type = self._TypeToJsType(prop.type_) 27 js_type = self._TypeToJsType(namespace_name, prop.type_)
168 if prop.optional: 28 if prop.optional:
169 js_type = (Code(). 29 js_type = (Code().
170 Append('('). 30 Append('(').
171 Concat(js_type, new_line=False). 31 Concat(js_type, new_line=False).
172 Append('|undefined)', new_line=False)) 32 Append('|undefined)', new_line=False))
173 c.Append('%s: ' % field, strip_right=False) 33 c.Append('%s: ' % field, strip_right=False)
174 c.Concat(js_type, new_line=False) 34 c.Concat(js_type, new_line=False)
175 35
176 c.Eblock('}') 36 c.Eblock('}')
177 37
178 return c 38 return c
179 39
180 def _GenerateFunctionJsDoc(self, function): 40 def GenerateFunctionJsDoc(self, namespace_name, function):
181 """Generates the documentation for a function as a Code. 41 """Generates the documentation for a function as a Code.
182 42
183 Returns an empty code object if the object has no documentation. 43 Returns an empty code object if the object has no documentation.
184 """ 44 """
185 c = Code() 45 c = Code()
186 c.Sblock(line='/**', line_prefix=' * ') 46 c.Sblock(line='/**', line_prefix=' * ')
187 47
188 if function.description: 48 if function.description:
189 c.Comment(function.description, comment_prefix='') 49 c.Comment(function.description, comment_prefix='')
190 50
191 def append_field(c, tag, js_type, name, optional, description): 51 def append_field(c, tag, js_type, name, optional, description):
192 c.Append('@%s {' % tag) 52 c.Append('@%s {' % tag)
193 c.Concat(js_type, new_line=False) 53 c.Concat(js_type, new_line=False)
194 if optional: 54 if optional:
195 c.Append('=', new_line=False) 55 c.Append('=', new_line=False)
196 c.Append('} %s' % name, new_line=False) 56 c.Append('} %s' % name, new_line=False)
197 if description: 57 if description:
198 c.Comment(' %s' % description, comment_prefix='', 58 c.Comment(' %s' % description, comment_prefix='',
199 wrap_indent=4, new_line=False) 59 wrap_indent=4, new_line=False)
200 60
201 for param in function.params: 61 for param in function.params:
202 append_field(c, 'param', self._TypeToJsType(param.type_), param.name, 62 append_field(c, 'param', self._TypeToJsType(namespace_name, param.type_),
203 param.optional, param.description) 63 param.name, param.optional, param.description)
204 64
205 if function.callback: 65 if function.callback:
206 append_field(c, 'param', self._FunctionToJsFunction(function.callback), 66 append_field(c, 'param',
67 self._FunctionToJsFunction(namespace_name,
68 function.callback),
207 function.callback.name, function.callback.optional, 69 function.callback.name, function.callback.optional,
208 function.callback.description) 70 function.callback.description)
209 71
210 if function.returns: 72 if function.returns:
211 append_field(c, 'return', self._TypeToJsType(function.returns), 73 append_field(c, 'return',
74 self._TypeToJsType(namespace_name, function.returns),
212 '', False, function.returns.description) 75 '', False, function.returns.description)
213 76
214 if function.deprecated: 77 if function.deprecated:
215 c.Append('@deprecated %s' % function.deprecated) 78 c.Append('@deprecated %s' % function.deprecated)
216 79
217 c.Append(self._GenerateSeeLink('method', function.name)) 80 c.Append(self.GenerateSeeLink(namespace_name, 'method', function.name))
218 81
219 c.Eblock(' */') 82 c.Eblock(' */')
220 return c 83 return c
221 84
222 def _FunctionToJsFunction(self, function): 85 def _FunctionToJsFunction(self, namespace_name, function):
223 """Converts a model.Function to a JS type (i.e., function([params])...)""" 86 """Converts a model.Function to a JS type (i.e., function([params])...)"""
224 c = Code() 87 c = Code()
225 c.Append('function(') 88 c.Append('function(')
226 for i, param in enumerate(function.params): 89 for i, param in enumerate(function.params):
227 c.Concat(self._TypeToJsType(param.type_), new_line=False) 90 c.Concat(self._TypeToJsType(namespace_name, param.type_), new_line=False)
228 if i is not len(function.params) - 1: 91 if i is not len(function.params) - 1:
229 c.Append(', ', new_line=False, strip_right=False) 92 c.Append(', ', new_line=False, strip_right=False)
230 c.Append('):', new_line=False) 93 c.Append('):', new_line=False)
231 94
232 if function.returns: 95 if function.returns:
233 c.Concat(self._TypeToJsType(function.returns), new_line=False) 96 c.Concat(self._TypeToJsType(namespace_name, function.returns),
97 new_line=False)
234 else: 98 else:
235 c.Append('void', new_line=False) 99 c.Append('void', new_line=False)
236 100
237 return c 101 return c
238 102
239 def _TypeToJsType(self, js_type): 103 def _TypeToJsType(self, namespace_name, js_type):
240 """Converts a model.Type to a JS type (number, Array, etc.)""" 104 """Converts a model.Type to a JS type (number, Array, etc.)"""
241 if js_type.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE): 105 if js_type.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE):
242 return Code().Append('number') 106 return Code().Append('number')
243 if js_type.property_type is PropertyType.OBJECT: 107 if js_type.property_type is PropertyType.OBJECT:
244 if js_type.properties: 108 if js_type.properties:
245 return self._GenerateObjectDefinition(js_type.properties) 109 return self.GenerateObjectDefinition(namespace_name,
110 js_type.properties)
246 return Code().Append('Object') 111 return Code().Append('Object')
247 if js_type.property_type is PropertyType.ARRAY: 112 if js_type.property_type is PropertyType.ARRAY:
248 return (Code().Append('!Array<'). 113 return (Code().Append('!Array<').
249 Concat(self._TypeToJsType(js_type.item_type), new_line=False). 114 Concat(self._TypeToJsType(namespace_name, js_type.item_type),
115 new_line=False).
250 Append('>', new_line=False)) 116 Append('>', new_line=False))
251 if js_type.property_type is PropertyType.REF: 117 if js_type.property_type is PropertyType.REF:
252 ref_type = '!chrome.%s.%s' % (self._namespace.name, js_type.ref_type) 118 ref_type = '!chrome.%s.%s' % (namespace_name, js_type.ref_type)
253 return Code().Append(ref_type) 119 return Code().Append(ref_type)
254 if js_type.property_type is PropertyType.CHOICES: 120 if js_type.property_type is PropertyType.CHOICES:
255 c = Code() 121 c = Code()
256 c.Append('(') 122 c.Append('(')
257 for i, choice in enumerate(js_type.choices): 123 for i, choice in enumerate(js_type.choices):
258 c.Concat(self._TypeToJsType(choice), new_line=False) 124 c.Concat(self._TypeToJsType(namespace_name, choice), new_line=False)
259 if i is not len(js_type.choices) - 1: 125 if i is not len(js_type.choices) - 1:
260 c.Append('|', new_line=False) 126 c.Append('|', new_line=False)
261 c.Append(')', new_line=False) 127 c.Append(')', new_line=False)
262 return c 128 return c
263 if js_type.property_type is PropertyType.FUNCTION: 129 if js_type.property_type is PropertyType.FUNCTION:
264 return self._FunctionToJsFunction(js_type.function) 130 return self._FunctionToJsFunction(namespace_name, js_type.function)
265 if js_type.property_type is PropertyType.ANY: 131 if js_type.property_type is PropertyType.ANY:
266 return Code().Append('*') 132 return Code().Append('*')
267 if js_type.property_type.is_fundamental: 133 if js_type.property_type.is_fundamental:
268 return Code().Append(js_type.property_type.name) 134 return Code().Append(js_type.property_type.name)
269 return Code().Append('?') # TODO(tbreisacher): Make this more specific. 135 return Code().Append('?') # TODO(tbreisacher): Make this more specific.
270 136
271 def _GenerateFunction(self, function): 137 def GenerateSeeLink(self, namespace_name, object_type, object_name):
272 """Generates the code representing a function, including its documentation.
273 For example:
274
275 /**
276 * @param {string} title The new title.
277 */
278 chrome.window.setTitle = function(title) {};
279 """
280 c = Code()
281 params = self._GenerateFunctionParams(function)
282 (c.Concat(self._GenerateFunctionJsDoc(function))
283 .Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name,
284 function.name,
285 params))
286 )
287 return c
288
289 def _GenerateEvent(self, event):
290 """Generates the code representing an event.
291 For example:
292
293 /** @type {!ChromeEvent} */
294 chrome.bookmarks.onChildrenReordered;
295 """
296 c = Code()
297 c.Sblock(line='/**', line_prefix=' * ')
298 if (event.description):
299 c.Comment(event.description, comment_prefix='')
300 c.Append('@type {!ChromeEvent}')
301 c.Append(self._GenerateSeeLink('event', event.name))
302 c.Eblock(' */')
303 c.Append('chrome.%s.%s;' % (self._namespace.name, event.name))
304 return c
305
306 def _GenerateNamespaceObject(self):
307 """Generates the code creating namespace object.
308 For example:
309
310 /**
311 * @const
312 */
313 chrome.bookmarks = {};
314 """
315 c = Code()
316 (c.Append("""/**
317 * @const
318 */""")
319 .Append('chrome.%s = {};' % self._namespace.name))
320 return c
321
322 def _GenerateFunctionParams(self, function):
323 params = function.params[:]
324 if function.callback:
325 params.append(function.callback)
326 return ', '.join(param.name for param in params)
327
328 def _GenerateSeeLink(self, object_type, object_name):
329 """Generates a @see link for a given API 'object' (type, method, or event). 138 """Generates a @see link for a given API 'object' (type, method, or event).
330 """ 139 """
331 140
332 # NOTE(devlin): This is kind of a hack. Some APIs will be hosted on 141 # NOTE(devlin): This is kind of a hack. Some APIs will be hosted on
333 # developer.chrome.com/apps/ instead of /extensions/, and some APIs have 142 # developer.chrome.com/apps/ instead of /extensions/, and some APIs have
334 # '.'s in them (like app.window), which should resolve to 'app_window'. 143 # '.'s in them (like app.window), which should resolve to 'app_window'.
335 # Luckily, the doc server has excellent url resolution, and knows exactly 144 # Luckily, the doc server has excellent url resolution, and knows exactly
336 # what we mean. This saves us from needing any complicated logic here. 145 # what we mean. This saves us from needing any complicated logic here.
337 return ('@see https://developer.chrome.com/extensions/%s#%s-%s' % 146 return ('@see https://developer.chrome.com/extensions/%s#%s-%s' %
338 (self._namespace.name, object_type, object_name)) 147 (namespace_name, object_type, object_name))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698