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

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: . 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 import re 18 import re
19 19
20 LICENSE = ("""// Copyright %s The Chromium Authors. All rights reserved. 20 NOTE = """// NOTE: The format of types has changed. 'FooType' is now
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'. 21 // 'chrome.%s.FooType'.
29 // Please run the closure compiler before committing changes. 22 // Please run the closure compiler before committing changes.
30 // See https://code.google.com/p/chromium/wiki/ClosureCompilation. 23 // See https://code.google.com/p/chromium/wiki/ClosureCompilation.
31 """ 24 """
32 25
33 class JsExternsGenerator(object): 26 class JsExternsGenerator(object):
34 def Generate(self, namespace): 27 def Generate(self, namespace):
35 return _Generator(namespace).Generate() 28 return _Generator(namespace).Generate()
36 29
30 def GetHeader(tool, namespace):
31 """Returns the file header text.
32 """
33 js_util = JsUtil();
34 return (js_util.GetLicense() + '\n' +
35 js_util.GetInfo(tool) + (NOTE % namespace) + '\n' +
36 ('/** @fileoverview Externs generated from namespace: %s */' %
37 namespace))
38 GetHeader = staticmethod(GetHeader)
39
40
37 class _Generator(object): 41 class _Generator(object):
38 def __init__(self, namespace): 42 def __init__(self, namespace):
39 self._namespace = namespace 43 self._namespace = namespace
44 self._js_util = JsUtil()
40 45
41 def Generate(self): 46 def Generate(self):
42 """Generates a Code object with the schema for the entire namespace. 47 """Generates a Code object with the schema for the entire namespace.
43 """ 48 """
44 c = Code() 49 c = Code()
45 (c.Append(LICENSE) 50 (c.Append(JsExternsGenerator.GetHeader(sys.argv[0],
46 .Append() 51 self._namespace.name))
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 .Append())
52 53
53 c.Cblock(self._GenerateNamespaceObject()) 54 c.Cblock(self._GenerateNamespaceObject())
54 55
55 for js_type in self._namespace.types.values(): 56 for js_type in self._namespace.types.values():
56 c.Cblock(self._GenerateType(js_type)) 57 c.Cblock(self._GenerateType(js_type))
57 58
58 for function in self._namespace.functions.values(): 59 for function in self._namespace.functions.values():
59 c.Cblock(self._GenerateFunction(function)) 60 c.Cblock(self._GenerateFunction(function))
60 61
61 for event in self._namespace.events.values(): 62 for event in self._namespace.events.values():
62 c.Cblock(self._GenerateEvent(event)) 63 c.Cblock(self._GenerateEvent(event))
63 64
65 c.TrimTrailingNewlines()
66
64 return c 67 return c
65 68
66 def _GenerateType(self, js_type): 69 def _GenerateType(self, js_type):
67 """Given a Type object, returns the Code for this type's definition. 70 """Given a Type object, returns the Code for this type's definition.
68 """ 71 """
69 c = Code() 72 c = Code()
70 if js_type.property_type is PropertyType.ENUM: 73 if js_type.property_type is PropertyType.ENUM:
71 c.Concat(self._GenerateEnumJsDoc(js_type)) 74 c.Concat(self._GenerateEnumJsDoc(js_type))
72 else: 75 else:
73 c.Concat(self._GenerateTypeJsDoc(js_type)) 76 c.Concat(self._GenerateTypeJsDoc(js_type))
74 77
75 return c 78 return c
76 79
77 def _GenerateEnumJsDoc(self, js_type): 80 def _GenerateEnumJsDoc(self, js_type):
78 """ Given an Enum Type object, returns the Code for the enum's definition. 81 """ Given an Enum Type object, returns the Code for the enum's definition.
79 """ 82 """
80 c = Code() 83 c = Code()
81 (c.Sblock(line='/**', line_prefix=' * ') 84 (c.Sblock(line='/**', line_prefix=' * ')
82 .Append('@enum {string}') 85 .Append('@enum {string}')
83 .Append(self._GenerateSeeLink('type', js_type.simple_name)) 86 .Append(self._js_util.GenerateSeeLink(self._namespace.name,'type',
michaelpg 2015/12/04 21:02:19 space
stevenjb 2015/12/07 18:31:24 "space" ??
michaelpg 2015/12/07 18:46:53 sorry, i guess a certain someone is rubbing off on
stevenjb 2015/12/07 19:49:32 Ah. Yes indeed :) Done.
87 js_type.simple_name))
84 .Eblock(' */')) 88 .Eblock(' */'))
85 c.Append('chrome.%s.%s = {' % (self._namespace.name, js_type.name)) 89 c.Append('chrome.%s.%s = {' % (self._namespace.name, js_type.name))
86 90
87 def get_property_name(e): 91 def get_property_name(e):
88 # Enum properties are normified to be in ALL_CAPS_STYLE. 92 # Enum properties are normified to be in ALL_CAPS_STYLE.
89 # Assume enum '1ring-rulesThemAll'. 93 # Assume enum '1ring-rulesThemAll'.
90 # Transform to '1ring-rules_Them_All'. 94 # Transform to '1ring-rules_Them_All'.
91 e = re.sub(r'([a-z])([A-Z])', r'\1_\2', e) 95 e = re.sub(r'([a-z])([A-Z])', r'\1_\2', e)
92 # Transform to '1ring_rules_Them_All'. 96 # Transform to '1ring_rules_Them_All'.
93 e = re.sub(r'\W', '_', e) 97 e = re.sub(r'\W', '_', e)
(...skipping 26 matching lines...) Expand all
120 if js_type.description: 124 if js_type.description:
121 for line in js_type.description.splitlines(): 125 for line in js_type.description.splitlines():
122 c.Append(line) 126 c.Append(line)
123 127
124 is_constructor = self._IsTypeConstructor(js_type) 128 is_constructor = self._IsTypeConstructor(js_type)
125 if is_constructor: 129 if is_constructor:
126 c.Comment('@constructor', comment_prefix = ' * ', wrap_indent=4) 130 c.Comment('@constructor', comment_prefix = ' * ', wrap_indent=4)
127 else: 131 else:
128 c.Concat(self._GenerateTypedef(js_type.properties)) 132 c.Concat(self._GenerateTypedef(js_type.properties))
129 133
130 c.Append(self._GenerateSeeLink('type', js_type.simple_name)) 134 c.Append(self._js_util.GenerateSeeLink(self._namespace.name,'type',
michaelpg 2015/12/04 21:02:19 space
stevenjb 2015/12/07 18:31:24 ?
michaelpg 2015/12/07 18:46:53 ditto
stevenjb 2015/12/07 19:49:32 Done.
135 js_type.simple_name))
131 c.Eblock(' */') 136 c.Eblock(' */')
132 137
133 var = 'chrome.%s.%s' % (js_type.namespace.name, js_type.simple_name) 138 var = 'chrome.%s.%s' % (js_type.namespace.name, js_type.simple_name)
134 if is_constructor: var += ' = function() {}' 139 if is_constructor: var += ' = function() {}'
135 var += ';' 140 var += ';'
136 c.Append(var) 141 c.Append(var)
137 142
138 return c 143 return c
139 144
140 def _GenerateTypedef(self, properties): 145 def _GenerateTypedef(self, properties):
141 """Given an OrderedDict of properties, returns a Code containing a @typedef. 146 """Given an OrderedDict of properties, returns a Code containing a @typedef.
142 """ 147 """
143 if not properties: return Code() 148 if not properties: return Code()
144 149
145 c = Code() 150 c = Code()
146 c.Append('@typedef {') 151 c.Append('@typedef {')
147 c.Concat(self._GenerateObjectDefinition(properties), new_line=False) 152 c.Concat(self._js_util.GenerateObjectDefinition(self._namespace.name,
153 properties),
154 new_line=False)
148 c.Append('}', new_line=False) 155 c.Append('}', new_line=False)
149 return c 156 return c
150 157
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): 158 def _GenerateFunction(self, function):
272 """Generates the code representing a function, including its documentation. 159 """Generates the code representing a function, including its documentation.
273 For example: 160 For example:
274 161
275 /** 162 /**
276 * @param {string} title The new title. 163 * @param {string} title The new title.
277 */ 164 */
278 chrome.window.setTitle = function(title) {}; 165 chrome.window.setTitle = function(title) {};
279 """ 166 """
280 c = Code() 167 c = Code()
281 params = self._GenerateFunctionParams(function) 168 params = self._GenerateFunctionParams(function)
282 (c.Concat(self._GenerateFunctionJsDoc(function)) 169 (c.Concat(self._js_util.GenerateFunctionJsDoc(self._namespace.name,
170 function))
283 .Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name, 171 .Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name,
284 function.name, 172 function.name,
285 params)) 173 params))
286 ) 174 )
287 return c 175 return c
288 176
289 def _GenerateEvent(self, event): 177 def _GenerateEvent(self, event):
290 """Generates the code representing an event. 178 """Generates the code representing an event.
291 For example: 179 For example:
292 180
293 /** @type {!ChromeEvent} */ 181 /** @type {!ChromeEvent} */
294 chrome.bookmarks.onChildrenReordered; 182 chrome.bookmarks.onChildrenReordered;
295 """ 183 """
296 c = Code() 184 c = Code()
297 c.Sblock(line='/**', line_prefix=' * ') 185 c.Sblock(line='/**', line_prefix=' * ')
298 if (event.description): 186 if (event.description):
299 c.Comment(event.description, comment_prefix='') 187 c.Comment(event.description, comment_prefix='')
300 c.Append('@type {!ChromeEvent}') 188 c.Append('@type {!ChromeEvent}')
301 c.Append(self._GenerateSeeLink('event', event.name)) 189 c.Append(self._js_util.GenerateSeeLink(self._namespace.name, 'event',
190 event.name))
302 c.Eblock(' */') 191 c.Eblock(' */')
303 c.Append('chrome.%s.%s;' % (self._namespace.name, event.name)) 192 c.Append('chrome.%s.%s;' % (self._namespace.name, event.name))
304 return c 193 return c
305 194
306 def _GenerateNamespaceObject(self): 195 def _GenerateNamespaceObject(self):
307 """Generates the code creating namespace object. 196 """Generates the code creating namespace object.
308 For example: 197 For example:
309 198
310 /** 199 /**
311 * @const 200 * @const
312 */ 201 */
313 chrome.bookmarks = {}; 202 chrome.bookmarks = {};
314 """ 203 """
315 c = Code() 204 c = Code()
316 (c.Append("""/** 205 (c.Append("""/**
317 * @const 206 * @const
318 */""") 207 */""")
319 .Append('chrome.%s = {};' % self._namespace.name)) 208 .Append('chrome.%s = {};' % self._namespace.name))
320 return c 209 return c
321 210
322 def _GenerateFunctionParams(self, function): 211 def _GenerateFunctionParams(self, function):
323 params = function.params[:] 212 params = function.params[:]
324 if function.callback: 213 if function.callback:
325 params.append(function.callback) 214 params.append(function.callback)
326 return ', '.join(param.name for param in params) 215 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