OLD | NEW |
---|---|
(Empty) | |
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. | |
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 | |
11 from code import Code | |
12 from model import * | |
13 from schema_util import * | |
14 | |
15 import os | |
16 from datetime import datetime | |
17 | |
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) | |
22 | |
23 class JsExternsGenerator(object): | |
24 def __init__(self): | |
25 pass | |
Dan Beam
2015/03/19 19:15:02
^ can't you just omit the __init__ in this case?
Devlin
2015/03/19 20:59:57
Done.
| |
26 | |
27 def Generate(self, namespace): | |
28 return _Generator(namespace).Generate() | |
29 | |
30 class _Generator(object): | |
31 def __init__(self, namespace): | |
32 self._namespace = namespace | |
33 | |
34 def Generate(self): | |
35 """Generates a Code object with the .dart for the entire namespace. | |
Dan Beam
2015/03/19 19:15:02
the .dart?
Devlin
2015/03/19 20:59:57
Done.
| |
36 """ | |
37 c = Code() | |
38 (c.Append(LICENSE) | |
39 .Append() | |
40 .Append('/** @fileoverview Externs generated from namespace: %s */' % | |
41 self._namespace.name) | |
42 .Append()) | |
Dan Beam
2015/03/19 19:15:03
why are these () needed?
Devlin
2015/03/19 20:59:57
Because python doesn't use traditional line breaks
Dan Beam
2015/03/23 21:58:42
it seems like this is already an expression so it
Devlin
2015/03/23 23:20:26
TL;DR: Python line wrapping sucks. This is needed
| |
43 | |
44 for type_ in self._namespace.types.values(): | |
Dan Beam
2015/03/19 19:15:02
maybe type_ -> js_type if we really can't use |typ
Devlin
2015/03/19 20:59:57
Done.
| |
45 c.Cblock(self._GenerateType(type_)) | |
46 | |
47 c.Cblock(self._GenerateNamespaceObject()) | |
48 | |
49 for function in self._namespace.functions.values(): | |
50 c.Cblock(self._GenerateFunction(function)) | |
51 | |
52 for event in self._namespace.events.values(): | |
53 c.Cblock(self._GenerateEvent(event)) | |
54 | |
55 return c | |
56 | |
57 def _GenerateType(self, type_): | |
58 """Given a Type object, returns the Code for this type's definition. | |
59 | |
60 """ | |
61 c = Code() | |
62 | |
63 # Since enums are just treated as strings for now, don't generate their | |
64 # type. | |
65 if type_.property_type is PropertyType.ENUM: | |
66 return c | |
67 | |
68 c.Concat(self._GenerateTypeJsDoc(type_)) | |
69 | |
70 if self._IsTypeConstructor(type_): | |
71 c.Append('var ' + type_.simple_name + ' = function();') | |
72 else: | |
73 c.Append('var ' + type_.simple_name + ';') | |
Dan Beam
2015/03/19 19:15:02
nit:
var = 'var ' + type_.simple_name
if self
Devlin
2015/03/19 20:59:57
Or even:
c.Append('var %s%s;' %
(
Dan Beam
2015/03/23 21:58:42
eh, up to you but Tyler's is probably the easiest
Devlin
2015/03/23 23:20:26
Well, I think yours will actually be 4, since I do
| |
74 | |
75 return c | |
76 | |
77 def _IsTypeConstructor(self, type_): | |
78 """Returns true if the given type should be a @constructor. If this returns | |
79 false, the type is a typedef. | |
80 """ | |
81 return any(prop.type_.property_type is PropertyType.FUNCTION | |
82 for prop in type_.properties.values()) | |
83 | |
84 def _GenerateTypeJsDoc(self, type_): | |
85 """Generates the documentation for a type as a Code. | |
86 | |
87 Returns an empty code object if the object has no documentation. | |
88 """ | |
89 c = Code() | |
90 c.Append('/**') | |
91 | |
92 if type_.description: | |
93 for line in type_.description.split('\n'): | |
Dan Beam
2015/03/19 19:15:03
nit: split('\n') -> splitlines()
Devlin
2015/03/19 20:59:57
Done.
| |
94 c.Comment(line, comment_prefix=' * ') | |
95 | |
96 if self._IsTypeConstructor(type_): | |
97 c.Comment('@constructor', comment_prefix = ' * ') | |
98 else: | |
99 c.Concat(self._GenerateTypedef(type_.properties)) | |
100 | |
101 c.Append(' */') | |
102 return c | |
103 | |
104 def _GenerateTypedef(self, properties): | |
105 """Given an OrderedDict of properties, returns a Code containing a @typedef. | |
106 """ | |
107 if not properties: return Code() | |
108 | |
109 lines = [] | |
110 lines.append('@typedef {{') | |
111 for field, property_ in properties.items(): | |
112 js_type = self._TypeToJsType(property_.type_) | |
113 if property_.optional: | |
114 js_type = '(%s|undefined)' % js_type | |
115 lines.append(' %s: %s,' % (field, js_type)) | |
116 | |
117 # remove last trailing comma | |
118 lines[-1] = lines[-1][:-1] | |
119 lines.append('}}') | |
120 # TODO(tbreisacher): Add '@see <link to documentation>'. | |
121 | |
122 lines = [' * ' + line for line in lines] | |
123 code_string = '\n'.join([' * ' + line for line in lines]) | |
124 c = Code() | |
125 c.Append(code_string) | |
126 return c | |
127 | |
128 def _GenerateFunctionJsDoc(self, function): | |
129 """Generates the documentation for a function as a Code. | |
130 | |
131 Returns an empty code object if the object has no documentation. | |
132 """ | |
133 c = Code() | |
134 c.Append('/**') | |
135 | |
136 if function.description: | |
137 for line in function.description.split('\n'): | |
138 c.Comment(line, comment_prefix=' * ') | |
139 | |
140 for param in function.params: | |
141 js_type = self._TypeToJsType(param.type_) | |
142 | |
143 if param.optional: | |
144 js_type += '=' | |
145 | |
146 param_doc = '@param {%s} %s %s' % (js_type, | |
147 param.name, | |
148 param.description or '') | |
149 c.Comment(param_doc, comment_prefix=' * ') | |
150 | |
151 if function.callback: | |
152 # TODO(tbreisacher): Convert Function to function() for better | |
153 # typechecking. | |
154 js_type = 'Function' | |
155 if function.callback.optional: | |
156 js_type += '=' | |
157 param_doc = '@param {%s} %s %s' % (js_type, | |
158 function.callback.name, | |
159 function.callback.description or '') | |
160 c.Comment(param_doc, comment_prefix=' * ') | |
161 | |
162 if function.returns: | |
163 return_doc = '@return {%s} %s' % (self._TypeToJsType(function.returns), | |
164 function.returns.description) | |
165 c.Comment(return_doc, comment_prefix=' * ') | |
166 | |
167 c.Append(' */') | |
168 return c | |
169 | |
170 def _TypeToJsType(self, type_): | |
171 """Converts a model.Type to a JS type (number, Array, etc.)""" | |
172 if type_.property_type in (PropertyType.INTEGER, PropertyType.DOUBLE): | |
173 return 'number' | |
174 elif type_.property_type is PropertyType.OBJECT: | |
175 return 'Object' | |
176 elif type_.property_type is PropertyType.ARRAY: | |
177 return 'Array' | |
178 elif type_.property_type is PropertyType.REF: | |
179 return type_.ref_type | |
180 elif type_.property_type.is_fundamental: | |
181 return type_.property_type.name | |
182 else: | |
183 return '?' # TODO(tbreisacher): Make this more specific. | |
184 | |
185 def _GenerateFunction(self, function): | |
186 """Generates the code representing a function, including its documentation. | |
187 For example: | |
188 | |
189 /** | |
190 * @param {string} title The new title. | |
191 */ | |
192 chrome.window.setTitle = function(title) {}; | |
193 """ | |
194 c = Code() | |
195 params = self._GenerateFunctionParams(function) | |
196 (c.Concat(self._GenerateFunctionJsDoc(function)) | |
197 .Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name, | |
198 function.name, | |
199 params)) | |
200 ) | |
201 return c | |
202 | |
203 def _GenerateEvent(self, event): | |
204 """Generates the code representing an event. | |
205 For example: | |
206 | |
207 /** @type {!ChromeEvent} */ | |
208 chrome.bookmarks.onChildrenReordered; | |
209 """ | |
210 c = Code() | |
211 (c.Append('/** @type {!ChromeEvent} */') | |
212 .Append('chrome.%s.%s;' % (self._namespace.name, event.name))) | |
213 return c | |
214 | |
215 def _GenerateNamespaceObject(self): | |
216 """Generates the code creating namespace object. | |
217 For example: | |
218 | |
219 /** | |
220 * @const | |
221 */ | |
222 chrome.bookmarks = {}; | |
223 """ | |
224 c = Code() | |
225 (c.Append("""/** | |
226 * @const | |
227 */""") | |
228 .Append('chrome.%s = {};' % self._namespace.name)) | |
229 return c | |
230 | |
231 def _GenerateFunctionParams(self, function): | |
232 params = function.params[:] | |
233 if function.callback: | |
234 params.append(function.callback) | |
235 return ', '.join(param.name for param in params) | |
OLD | NEW |