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

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

Powered by Google App Engine
This is Rietveld 408576698