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