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.
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
13 from schema_util
import *
16 from datetime
import datetime
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
33 """Generates a Code object with the schema for the entire namespace.
38 .Append('/** @fileoverview Externs generated from namespace: %s */' %
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
))
55 def _GenerateType(self
, js_type
):
56 """Given a Type object, returns the Code for this type's definition.
59 if js_type
.property_type
is PropertyType
.ENUM
:
60 c
.Concat(self
._GenerateEnumJsDoc
(js_type
))
62 c
.Concat(self
._GenerateTypeJsDoc
(js_type
))
66 def _GenerateEnumJsDoc(self
, js_type
):
67 """ Given an Enum Type object, returns the Code for the enum's definition.
70 (c
.Sblock(line
='/**', line_prefix
=' * ')
71 .Append('@enum {string}')
72 .Append(self
._GenerateSeeLink
('type', js_type
.simple_name
))
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'.
89 [" %s: '%s'," % (get_property_name(v
.name
), v
.name
)
90 for v
in js_type
.enum_values
]))
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.
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.
107 c
.Sblock(line
='/**', line_prefix
=' * ')
109 if js_type
.description
:
110 for line
in js_type
.description
.splitlines():
113 is_constructor
= self
._IsTypeConstructor
(js_type
)
115 c
.Comment('@constructor', comment_prefix
= ' * ', wrap_indent
=4)
117 c
.Concat(self
._GenerateTypedef
(js_type
.properties
))
119 c
.Append(self
._GenerateSeeLink
('type', js_type
.simple_name
))
122 var
= 'var ' + js_type
.simple_name
123 if is_constructor
: var
+= ' = function() {}'
129 def _GenerateTypedef(self
, properties
):
130 """Given an OrderedDict of properties, returns a Code containing a @typedef.
132 if not properties
: return Code()
135 c
.Append('@typedef {')
136 c
.Concat(self
._GenerateObjectDefinition
(properties
), new_line
=False)
137 c
.Append('}', new_line
=False)
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()
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.
154 c
.Append(',', new_line
=False)
156 js_type
= self
._TypeToJsType
(prop
.type_
)
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)
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.
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)
184 c
.Append('=', new_line
=False)
185 c
.Append('} %s' % name
, new_line
=False)
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
)
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
))
211 def _FunctionToJsFunction(self
, function
):
212 """Converts a model.Function to a JS type (i.e., function([params])...)"""
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)
222 c
.Concat(self
._TypeToJsType
(function
.returns
), new_line
=False)
224 c
.Append('void', new_line
=False)
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
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
:
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)
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.
269 * @param {string} title The new title.
271 chrome.window.setTitle = function(title) {};
274 params
= self
._GenerateFunctionParams
(function
)
275 (c
.Concat(self
._GenerateFunctionJsDoc
(function
))
276 .Append('chrome.%s.%s = function(%s) {};' % (self
._namespace
.name
,
282 def _GenerateEvent(self
, event
):
283 """Generates the code representing an event.
286 /** @type {!ChromeEvent} */
287 chrome.bookmarks.onChildrenReordered;
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
))
296 c
.Append('chrome.%s.%s;' % (self
._namespace
.name
, event
.name
))
299 def _GenerateNamespaceObject(self
):
300 """Generates the code creating namespace object.
306 chrome.bookmarks = {};
312 .Append('chrome.%s = {};' % self
._namespace
.name
))
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
))