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
18 LICENSE
= ("""// Copyright %s The Chromium Authors. All rights reserved.
19 // Use of this source code is governed by a BSD-style license that can be
20 // found in the LICENSE file.
21 """ % datetime
.now().year
)
23 class JsExternsGenerator(object):
24 def Generate(self
, namespace
):
25 return _Generator(namespace
).Generate()
27 class _Generator(object):
28 def __init__(self
, namespace
):
29 self
._namespace
= namespace
32 """Generates a Code object with the schema for the entire namespace.
37 .Append('/** @fileoverview Externs generated from namespace: %s */' %
41 c
.Cblock(self
._GenerateNamespaceObject
())
43 for js_type
in self
._namespace
.types
.values():
44 c
.Cblock(self
._GenerateType
(js_type
))
46 for function
in self
._namespace
.functions
.values():
47 c
.Cblock(self
._GenerateFunction
(function
))
49 for event
in self
._namespace
.events
.values():
50 c
.Cblock(self
._GenerateEvent
(event
))
54 def _GenerateType(self
, js_type
):
55 """Given a Type object, returns the Code for this type's definition.
58 if js_type
.property_type
is PropertyType
.ENUM
:
59 c
.Concat(self
._GenerateEnumJsDoc
(js_type
))
61 c
.Concat(self
._GenerateTypeJsDoc
(js_type
))
65 def _GenerateEnumJsDoc(self
, js_type
):
66 """ Given an Enum Type object, returns the Code for the enum's definition.
69 (c
.Sblock(line
='/**', line_prefix
=' * ')
70 .Append('@enum {string}')
71 .Append(self
._GenerateSeeLink
('type', js_type
.simple_name
))
73 c
.Append('chrome.%s.%s = {' % (self
._namespace
.name
, js_type
.name
))
75 [" %s: '%s'," % (v
.name
, v
.name
) for v
in js_type
.enum_values
]))
79 def _IsTypeConstructor(self
, js_type
):
80 """Returns true if the given type should be a @constructor. If this returns
81 false, the type is a typedef.
83 return any(prop
.type_
.property_type
is PropertyType
.FUNCTION
84 for prop
in js_type
.properties
.values())
86 def _GenerateTypeJsDoc(self
, js_type
):
87 """Generates the documentation for a type as a Code.
89 Returns an empty code object if the object has no documentation.
92 c
.Sblock(line
='/**', line_prefix
=' * ')
94 if js_type
.description
:
95 for line
in js_type
.description
.splitlines():
98 is_constructor
= self
._IsTypeConstructor
(js_type
)
100 c
.Comment('@constructor', comment_prefix
= ' * ', wrap_indent
=4)
102 c
.Concat(self
._GenerateTypedef
(js_type
.properties
))
104 c
.Append(self
._GenerateSeeLink
('type', js_type
.simple_name
))
107 var
= 'var ' + js_type
.simple_name
108 if is_constructor
: var
+= ' = function() {}'
114 def _GenerateTypedef(self
, properties
):
115 """Given an OrderedDict of properties, returns a Code containing a @typedef.
117 if not properties
: return Code()
120 c
.Append('@typedef {')
121 c
.Concat(self
._GenerateObjectDefinition
(properties
), new_line
=False)
122 c
.Append('}', new_line
=False)
125 def _GenerateObjectDefinition(self
, properties
):
126 """Given an OrderedDict of properties, returns a Code containing the
127 description of an object.
129 if not properties
: return Code()
134 for field
, prop
in properties
.items():
135 # Avoid trailing comma.
136 # TODO(devlin): This will be unneeded, if/when
137 # https://github.com/google/closure-compiler/issues/796 is fixed.
139 c
.Append(',', new_line
=False)
141 js_type
= self
._TypeToJsType
(prop
.type_
)
145 Concat(js_type
, new_line
=False).
146 Append('|undefined)', new_line
=False))
147 c
.Append('%s: ' % field
, strip_right
=False)
148 c
.Concat(js_type
, new_line
=False)
154 def _GenerateFunctionJsDoc(self
, function
):
155 """Generates the documentation for a function as a Code.
157 Returns an empty code object if the object has no documentation.
160 c
.Sblock(line
='/**', line_prefix
=' * ')
162 if function
.description
:
163 c
.Comment(function
.description
, comment_prefix
='')
165 def append_field(c
, tag
, js_type
, name
, optional
, description
):
166 c
.Append('@%s {' % tag
)
167 c
.Concat(js_type
, new_line
=False)
169 c
.Append('=', new_line
=False)
170 c
.Append('} %s' % name
, new_line
=False)
172 c
.Comment(' %s' % description
, comment_prefix
='',
173 wrap_indent
=4, new_line
=False)
175 for param
in function
.params
:
176 append_field(c
, 'param', self
._TypeToJsType
(param
.type_
), param
.name
,
177 param
.optional
, param
.description
)
179 if function
.callback
:
180 append_field(c
, 'param', self
._FunctionToJsFunction
(function
.callback
),
181 function
.callback
.name
, function
.callback
.optional
,
182 function
.callback
.description
)
185 append_field(c
, 'return', self
._TypeToJsType
(function
.returns
),
186 '', False, function
.returns
.description
)
188 if function
.deprecated
:
189 c
.Append('@deprecated %s' % function
.deprecated
)
191 c
.Append(self
._GenerateSeeLink
('method', function
.name
))
196 def _FunctionToJsFunction(self
, function
):
197 """Converts a model.Function to a JS type (i.e., function([params])...)"""
199 c
.Append('function(')
200 for i
, param
in enumerate(function
.params
):
201 c
.Concat(self
._TypeToJsType
(param
.type_
), new_line
=False)
202 if i
is not len(function
.params
) - 1:
203 c
.Append(', ', new_line
=False, strip_right
=False)
204 c
.Append('):', new_line
=False)
207 c
.Concat(self
._TypeToJsType
(function
.returns
), new_line
=False)
209 c
.Append('void', new_line
=False)
213 def _TypeToJsType(self
, js_type
):
214 """Converts a model.Type to a JS type (number, Array, etc.)"""
215 if js_type
.property_type
in (PropertyType
.INTEGER
, PropertyType
.DOUBLE
):
216 return Code().Append('number')
217 if js_type
.property_type
is PropertyType
.OBJECT
:
218 if js_type
.properties
:
219 return self
._GenerateObjectDefinition
(js_type
.properties
)
220 return Code().Append('Object')
221 if js_type
.property_type
is PropertyType
.ARRAY
:
222 return (Code().Append('!Array<').
223 Concat(self
._TypeToJsType
(js_type
.item_type
), new_line
=False).
224 Append('>', new_line
=False))
225 if js_type
.property_type
is PropertyType
.REF
:
226 ref_type
= js_type
.ref_type
227 # Enums are defined as chrome.fooAPI.MyEnum, but types are defined simply
229 if self
._namespace
.types
[ref_type
].property_type
is PropertyType
.ENUM
:
230 ref_type
= '!chrome.%s.%s' % (self
._namespace
.name
, ref_type
)
231 return Code().Append(ref_type
)
232 if js_type
.property_type
is PropertyType
.CHOICES
:
235 for i
, choice
in enumerate(js_type
.choices
):
236 c
.Concat(self
._TypeToJsType
(choice
), new_line
=False)
237 if i
is not len(js_type
.choices
) - 1:
238 c
.Append('|', new_line
=False)
239 c
.Append(')', new_line
=False)
241 if js_type
.property_type
is PropertyType
.FUNCTION
:
242 return self
._FunctionToJsFunction
(js_type
.function
)
243 if js_type
.property_type
is PropertyType
.ANY
:
244 return Code().Append('*')
245 if js_type
.property_type
.is_fundamental
:
246 return Code().Append(js_type
.property_type
.name
)
247 return Code().Append('?') # TODO(tbreisacher): Make this more specific.
249 def _GenerateFunction(self
, function
):
250 """Generates the code representing a function, including its documentation.
254 * @param {string} title The new title.
256 chrome.window.setTitle = function(title) {};
259 params
= self
._GenerateFunctionParams
(function
)
260 (c
.Concat(self
._GenerateFunctionJsDoc
(function
))
261 .Append('chrome.%s.%s = function(%s) {};' % (self
._namespace
.name
,
267 def _GenerateEvent(self
, event
):
268 """Generates the code representing an event.
271 /** @type {!ChromeEvent} */
272 chrome.bookmarks.onChildrenReordered;
275 c
.Sblock(line
='/**', line_prefix
=' * ')
276 if (event
.description
):
277 c
.Comment(event
.description
, comment_prefix
='')
278 c
.Append('@type {!ChromeEvent}')
279 c
.Append(self
._GenerateSeeLink
('event', event
.name
))
281 c
.Append('chrome.%s.%s;' % (self
._namespace
.name
, event
.name
))
284 def _GenerateNamespaceObject(self
):
285 """Generates the code creating namespace object.
291 chrome.bookmarks = {};
297 .Append('chrome.%s = {};' % self
._namespace
.name
))
300 def _GenerateFunctionParams(self
, function
):
301 params
= function
.params
[:]
302 if function
.callback
:
303 params
.append(function
.callback
)
304 return ', '.join(param
.name
for param
in params
)
306 def _GenerateSeeLink(self
, object_type
, object_name
):
307 """Generates a @see link for a given API 'object' (type, method, or event).
310 # NOTE(devlin): This is kind of a hack. Some APIs will be hosted on
311 # developer.chrome.com/apps/ instead of /extensions/, and some APIs have
312 # '.'s in them (like app.window), which should resolve to 'app_window'.
313 # Luckily, the doc server has excellent url resolution, and knows exactly
314 # what we mean. This saves us from needing any complicated logic here.
315 return ('@see https://developer.chrome.com/extensions/%s#%s-%s' %
316 (self
._namespace
.name
, object_type
, object_name
))