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

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: Rebase + shorten line 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
16 import sys
17 from datetime import datetime 8 from datetime import datetime
18 import re
19 9
20 LICENSE = ("""// Copyright %s The Chromium Authors. All rights reserved. 10 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 11 // Use of this source code is governed by a BSD-style license that can be
22 // found in the LICENSE file. 12 // found in the LICENSE file.
23 """ % datetime.now().year) 13 """
24 14
25 INFO = """// This file was generated by: 15 INFO = """// This file was generated by:
26 // %s. 16 // %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 """ 17 """
32 18
33 class JsExternsGenerator(object):
34 def Generate(self, namespace):
35 return _Generator(namespace).Generate()
36 19
37 class _Generator(object): 20 class JsUtil(object):
38 def __init__(self, namespace): 21 """A helper class for generating JS Code.
39 self._namespace = namespace 22 """
23 def GetLicense(self):
24 """Returns the license text for JS extern and interface files.
25 """
26 return (LICENSE % datetime.now().year)
40 27
41 def Generate(self): 28 def GetInfo(self, tool):
42 """Generates a Code object with the schema for the entire namespace. 29 """Returns text describing how the file was generated.
43 """ 30 """
44 c = Code() 31 return (INFO % tool)
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 32
53 c.Cblock(self._GenerateNamespaceObject()) 33 def GenerateObjectDefinition(self, namespace_name, properties):
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 34 """Given an OrderedDict of properties, returns a Code containing the
153 description of an object. 35 description of an object.
154 """ 36 """
155 if not properties: return Code() 37 if not properties: return Code()
156 38
157 c = Code() 39 c = Code()
158 c.Sblock('{') 40 c.Sblock('{')
159 first = True 41 first = True
160 for field, prop in properties.items(): 42 for field, prop in properties.items():
161 # Avoid trailing comma. 43 # Avoid trailing comma.
162 # TODO(devlin): This will be unneeded, if/when 44 # TODO(devlin): This will be unneeded, if/when
163 # https://github.com/google/closure-compiler/issues/796 is fixed. 45 # https://github.com/google/closure-compiler/issues/796 is fixed.
164 if not first: 46 if not first:
165 c.Append(',', new_line=False) 47 c.Append(',', new_line=False)
166 first = False 48 first = False
167 js_type = self._TypeToJsType(prop.type_) 49 js_type = self._TypeToJsType(namespace_name, prop.type_)
168 if prop.optional: 50 if prop.optional:
169 js_type = (Code(). 51 js_type = (Code().
170 Append('('). 52 Append('(').
171 Concat(js_type, new_line=False). 53 Concat(js_type, new_line=False).
172 Append('|undefined)', new_line=False)) 54 Append('|undefined)', new_line=False))
173 c.Append('%s: ' % field, strip_right=False) 55 c.Append('%s: ' % field, strip_right=False)
174 c.Concat(js_type, new_line=False) 56 c.Concat(js_type, new_line=False)
175 57
176 c.Eblock('}') 58 c.Eblock('}')
177 59
178 return c 60 return c
179 61
180 def _GenerateFunctionJsDoc(self, function): 62 def GenerateFunctionJsDoc(self, namespace_name, function):
181 """Generates the documentation for a function as a Code. 63 """Generates the documentation for a function as a Code.
182 64
183 Returns an empty code object if the object has no documentation. 65 Returns an empty code object if the object has no documentation.
184 """ 66 """
185 c = Code() 67 c = Code()
186 c.Sblock(line='/**', line_prefix=' * ') 68 c.Sblock(line='/**', line_prefix=' * ')
187 69
188 if function.description: 70 if function.description:
189 c.Comment(function.description, comment_prefix='') 71 c.Comment(function.description, comment_prefix='')
190 72
191 def append_field(c, tag, js_type, name, optional, description): 73 def append_field(c, tag, js_type, name, optional, description):
192 c.Append('@%s {' % tag) 74 c.Append('@%s {' % tag)
193 c.Concat(js_type, new_line=False) 75 c.Concat(js_type, new_line=False)
194 if optional: 76 if optional:
195 c.Append('=', new_line=False) 77 c.Append('=', new_line=False)
196 c.Append('} %s' % name, new_line=False) 78 c.Append('} %s' % name, new_line=False)
197 if description: 79 if description:
198 c.Comment(' %s' % description, comment_prefix='', 80 c.Comment(' %s' % description, comment_prefix='',
199 wrap_indent=4, new_line=False) 81 wrap_indent=4, new_line=False)
200 82
201 for param in function.params: 83 for param in function.params:
202 append_field(c, 'param', self._TypeToJsType(param.type_), param.name, 84 append_field(c, 'param', self._TypeToJsType(namespace_name, param.type_),
203 param.optional, param.description) 85 param.name, param.optional, param.description)
204 86
205 if function.callback: 87 if function.callback:
206 append_field(c, 'param', self._FunctionToJsFunction(function.callback), 88 append_field(c, 'param',
89 self._FunctionToJsFunction(namespace_name,
90 function.callback),
207 function.callback.name, function.callback.optional, 91 function.callback.name, function.callback.optional,
208 function.callback.description) 92 function.callback.description)
209 93
210 if function.returns: 94 if function.returns:
211 append_field(c, 'return', self._TypeToJsType(function.returns), 95 append_field(c, 'return',
96 self._TypeToJsType(namespace_name, function.returns),
212 '', False, function.returns.description) 97 '', False, function.returns.description)
213 98
214 if function.deprecated: 99 if function.deprecated:
215 c.Append('@deprecated %s' % function.deprecated) 100 c.Append('@deprecated %s' % function.deprecated)
216 101
217 c.Append(self._GenerateSeeLink('method', function.name)) 102 c.Append(self.GenerateSeeLink(namespace_name, 'method', function.name))
218 103
219 c.Eblock(' */') 104 c.Eblock(' */')
220 return c 105 return c
221 106
222 def _FunctionToJsFunction(self, function): 107 def _FunctionToJsFunction(self, namespace_name, function):
223 """Converts a model.Function to a JS type (i.e., function([params])...)""" 108 """Converts a model.Function to a JS type (i.e., function([params])...)"""
224 c = Code() 109 c = Code()
225 c.Append('function(') 110 c.Append('function(')
226 for i, param in enumerate(function.params): 111 for i, param in enumerate(function.params):
227 c.Concat(self._TypeToJsType(param.type_), new_line=False) 112 c.Concat(self._TypeToJsType(namespace_name, param.type_), new_line=False)
228 if i is not len(function.params) - 1: 113 if i is not len(function.params) - 1:
229 c.Append(', ', new_line=False, strip_right=False) 114 c.Append(', ', new_line=False, strip_right=False)
230 c.Append('):', new_line=False) 115 c.Append('):', new_line=False)
231 116
232 if function.returns: 117 if function.returns:
233 c.Concat(self._TypeToJsType(function.returns), new_line=False) 118 c.Concat(self._TypeToJsType(namespace_name, function.returns),
119 new_line=False)
234 else: 120 else:
235 c.Append('void', new_line=False) 121 c.Append('void', new_line=False)
236 122
237 return c 123 return c
238 124
239 def _TypeToJsType(self, js_type): 125 def _TypeToJsType(self, namespace_name, js_type):
240 """Converts a model.Type to a JS type (number, Array, etc.)""" 126 """Converts a model.Type to a JS type (number, Array, etc.)"""
241 if js_type.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE): 127 if js_type.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE):
242 return Code().Append('number') 128 return Code().Append('number')
243 if js_type.property_type is PropertyType.OBJECT: 129 if js_type.property_type is PropertyType.OBJECT:
244 if js_type.properties: 130 if js_type.properties:
245 return self._GenerateObjectDefinition(js_type.properties) 131 return self.GenerateObjectDefinition(namespace_name,
132 js_type.properties)
246 return Code().Append('Object') 133 return Code().Append('Object')
247 if js_type.property_type is PropertyType.ARRAY: 134 if js_type.property_type is PropertyType.ARRAY:
248 return (Code().Append('!Array<'). 135 return (Code().Append('!Array<').
249 Concat(self._TypeToJsType(js_type.item_type), new_line=False). 136 Concat(self._TypeToJsType(namespace_name, js_type.item_type),
137 new_line=False).
250 Append('>', new_line=False)) 138 Append('>', new_line=False))
251 if js_type.property_type is PropertyType.REF: 139 if js_type.property_type is PropertyType.REF:
252 ref_type = '!chrome.%s.%s' % (self._namespace.name, js_type.ref_type) 140 ref_type = '!chrome.%s.%s' % (namespace_name, js_type.ref_type)
253 return Code().Append(ref_type) 141 return Code().Append(ref_type)
254 if js_type.property_type is PropertyType.CHOICES: 142 if js_type.property_type is PropertyType.CHOICES:
255 c = Code() 143 c = Code()
256 c.Append('(') 144 c.Append('(')
257 for i, choice in enumerate(js_type.choices): 145 for i, choice in enumerate(js_type.choices):
258 c.Concat(self._TypeToJsType(choice), new_line=False) 146 c.Concat(self._TypeToJsType(namespace_name, choice), new_line=False)
259 if i is not len(js_type.choices) - 1: 147 if i is not len(js_type.choices) - 1:
260 c.Append('|', new_line=False) 148 c.Append('|', new_line=False)
261 c.Append(')', new_line=False) 149 c.Append(')', new_line=False)
262 return c 150 return c
263 if js_type.property_type is PropertyType.FUNCTION: 151 if js_type.property_type is PropertyType.FUNCTION:
264 return self._FunctionToJsFunction(js_type.function) 152 return self._FunctionToJsFunction(namespace_name, js_type.function)
265 if js_type.property_type is PropertyType.ANY: 153 if js_type.property_type is PropertyType.ANY:
266 return Code().Append('*') 154 return Code().Append('*')
267 if js_type.property_type.is_fundamental: 155 if js_type.property_type.is_fundamental:
268 return Code().Append(js_type.property_type.name) 156 return Code().Append(js_type.property_type.name)
269 return Code().Append('?') # TODO(tbreisacher): Make this more specific. 157 return Code().Append('?') # TODO(tbreisacher): Make this more specific.
270 158
271 def _GenerateFunction(self, function): 159 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). 160 """Generates a @see link for a given API 'object' (type, method, or event).
330 """ 161 """
331 162
332 # NOTE(devlin): This is kind of a hack. Some APIs will be hosted on 163 # 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 164 # developer.chrome.com/apps/ instead of /extensions/, and some APIs have
334 # '.'s in them (like app.window), which should resolve to 'app_window'. 165 # '.'s in them (like app.window), which should resolve to 'app_window'.
335 # Luckily, the doc server has excellent url resolution, and knows exactly 166 # Luckily, the doc server has excellent url resolution, and knows exactly
336 # what we mean. This saves us from needing any complicated logic here. 167 # what we mean. This saves us from needing any complicated logic here.
337 return ('@see https://developer.chrome.com/extensions/%s#%s-%s' % 168 return ('@see https://developer.chrome.com/extensions/%s#%s-%s' %
338 (self._namespace.name, object_type, object_name)) 169 (namespace_name, object_type, object_name))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698