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

Side by Side Diff: utils/apidoc/html_diff.dart

Issue 10068019: Fix ApiDoc to work with new dart:html code. Tweak dart:html generator to generate clearer @domName … (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review fixes Created 8 years, 8 months 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 | Annotate | Revision Log
« no previous file with comments | « utils/apidoc/apidoc.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * A script to assist in documenting the difference between the dart:html API 6 * A script to assist in documenting the difference between the dart:html API
7 * and the old DOM API. 7 * and the old DOM API.
8 */ 8 */
9 #library('html_diff'); 9 #library('html_diff');
10 10
11 #import('dart:coreimpl'); 11 #import('dart:coreimpl');
12
12 #import('../../frog/lang.dart'); 13 #import('../../frog/lang.dart');
13 #import('../../frog/file_system_vm.dart'); 14 #import('../../frog/file_system_vm.dart');
14 #import('../../frog/file_system.dart'); 15 #import('../../frog/file_system.dart');
15 #import('../../lib/dartdoc/dartdoc.dart'); 16 #import('../../lib/dartdoc/dartdoc.dart');
16 17
17 /** 18 /**
18 * A class for computing a many-to-many mapping between the types and members in 19 * A class for computing a many-to-many mapping between the types and members in
19 * `dart:dom` and `dart:html`. This mapping is based on two indicators: 20 * `dart:dom` and `dart:html`. This mapping is based on two indicators:
20 * 21 *
21 * 1. Auto-detected wrappers. Most `dart:html` types correspond 22 * 1. Auto-detected wrappers. Most `dart:html` types correspond
(...skipping 30 matching lines...) Expand all
52 53
53 static Library dom; 54 static Library dom;
54 55
55 /** 56 /**
56 * Perform static initialization of [world]. This should be run before 57 * Perform static initialization of [world]. This should be run before
57 * calling [HtmlDiff.run]. 58 * calling [HtmlDiff.run].
58 */ 59 */
59 static void initialize() { 60 static void initialize() {
60 world.getOrAddLibrary('dart:dom'); 61 world.getOrAddLibrary('dart:dom');
61 world.getOrAddLibrary('dart:html'); 62 world.getOrAddLibrary('dart:html');
62 world.resolveAll(); 63 world.process();
64
63 dom = world.libraries['dart:dom']; 65 dom = world.libraries['dart:dom'];
64 } 66 }
65 67
66 HtmlDiff() : 68 HtmlDiff() :
67 domToHtml = new Map<Member, Set<Member>>(), 69 domToHtml = new Map<Member, Set<Member>>(),
68 htmlToDom = new Map<Member, Set<Member>>(), 70 htmlToDom = new Map<Member, Set<Member>>(),
69 domTypesToHtml = new Map<Type, Set<Type>>(), 71 domTypesToHtml = new Map<Type, Set<Type>>(),
70 htmlTypesToDom = new Map<Type, Set<Type>>(), 72 htmlTypesToDom = new Map<Type, Set<Type>>(),
71 comments = new CommentMap(); 73 comments = new CommentMap();
72 74
73 /** 75 /**
74 * Computes the `dart:dom` to `dart:html` mapping, and places it in 76 * Computes the `dart:dom` to `dart:html` mapping, and places it in
75 * [domToHtml], [htmlToDom], [domTypesToHtml], and [htmlTypesToDom]. Before 77 * [domToHtml], [htmlToDom], [domTypesToHtml], and [htmlTypesToDom]. Before
76 * this is run, Frog should be initialized (via [parseOptions] and 78 * this is run, Frog should be initialized (via [parseOptions] and
77 * [initializeWorld]) and [HtmlDiff.initialize] should be called. 79 * [initializeWorld]) and [HtmlDiff.initialize] should be called.
78 */ 80 */
79 void run() { 81 void run() {
80 final htmlLib = world.libraries['dart:html']; 82 final htmlLib = world.libraries['dart:html'];
81 for (var implType in htmlLib.types.getValues()) { 83 for (Type htmlType in htmlLib.types.getValues()) {
82 final domTypes = htmlToDomTypes(implType); 84 final domTypes = htmlToDomTypes(htmlType);
83 final htmlType = htmlImplToHtmlType(implType); 85 if (domTypes.isEmpty()) continue;
84 if (htmlType == null) continue;
85 86
86 htmlTypesToDom.putIfAbsent(htmlType, () => new Set()).addAll(domTypes); 87 htmlTypesToDom.putIfAbsent(htmlType,
88 () => new Set()).addAll(domTypes);
87 domTypes.forEach((t) => 89 domTypes.forEach((t) =>
88 domTypesToHtml.putIfAbsent(t, () => new Set()).add(htmlType)); 90 domTypesToHtml.putIfAbsent(t, () => new Set()).add(htmlType));
89 91
90 final members = new List.from(implType.members.getValues()); 92 final members = new List.from(htmlType.members.getValues());
91 members.addAll(implType.constructors.getValues()); 93 members.addAll(htmlType.constructors.getValues());
92 implType.factories.forEach((f) => members.add(f)); 94 htmlType.factories.forEach((f) => members.add(f));
93 members.forEach((m) => _addMemberDiff(m, domTypes)); 95 members.forEach((m) => _addMemberDiff(m, domTypes));
94 } 96 }
95 } 97 }
96 98
97 /** 99 /**
98 * Returns whether or not [domMember] (from `dart:dom`) and [htmlMember] (from
99 * `dart:html`) have the same name from the user's perspective. The names are
100 * the same if the type names and the member names are the same, but allowance
101 * is made for `dart:dom` names that start with "HTML" or "WebKit", and for
102 * `dart:html` properties that have the same name as fields in `dart:dom`.
103 */
104 bool sameName(Member domMember, Member htmlMember) {
105 var domTypeName = domMember.declaringType.name;
106 if (domTypeName == 'DOMWindow') domTypeName = 'Window';
107 domTypeName = domTypeName.replaceFirst(new RegExp('^(HTML|WebKit)'), '');
108 var htmlTypeName = htmlMember.declaringType.name;
109
110 var domName = domMember.name;
111 var htmlName = htmlMember.name;
112 if (htmlName.startsWith('get:') || htmlName.startsWith('set:')) {
113 htmlName = htmlName.substring(4);
114 }
115
116 return domTypeName == htmlTypeName && domName == htmlName;
117 }
118
119 /**
120 * Records the `dart:dom` to `dart:html` mapping for [implMember] (from 100 * Records the `dart:dom` to `dart:html` mapping for [implMember] (from
121 * `dart:html`). [domTypes] are the `dart:dom` [Type]s that correspond to 101 * `dart:html`). [domTypes] are the `dart:dom` [Type]s that correspond to
122 * [implMember]'s defining [Type]. 102 * [implMember]'s defining [Type].
123 */ 103 */
124 void _addMemberDiff(Member implMember, List<Type> domTypes) { 104 void _addMemberDiff(Member htmlMember, List<Type> domTypes) {
125 if (implMember.isProperty) { 105 if (htmlMember.isProperty) {
126 if (implMember.canGet) _addMemberDiff(implMember.getter, domTypes); 106 if (htmlMember.canGet) _addMemberDiff(htmlMember.getter, domTypes);
127 if (implMember.canSet) _addMemberDiff(implMember.setter, domTypes); 107 if (htmlMember.canSet) _addMemberDiff(htmlMember.setter, domTypes);
128 } 108 }
129 109
130 var domMembers = htmlToDomMembers(implMember, domTypes); 110 var domMembers = htmlToDomMembers(htmlMember, domTypes);
131 var htmlMember = htmlImplToHtmlMember(implMember);
132 if (htmlMember == null && !domMembers.isEmpty()) { 111 if (htmlMember == null && !domMembers.isEmpty()) {
133 print('Warning: dart:html member ${implMember.declaringType.name}.' + 112 print('Warning: dart:html member ${htmlMember.declaringType.name}.' +
134 '${implMember.name} has no corresponding dart:html member.'); 113 '${htmlMember.name} has no corresponding dart:html member.');
135 } 114 }
136 115
137 if (htmlMember == null) return; 116 if (htmlMember == null) return;
138 if (!domMembers.isEmpty()) htmlToDom[htmlMember] = domMembers; 117 if (!domMembers.isEmpty()) htmlToDom[htmlMember] = domMembers;
139 domMembers.forEach((m) => 118 domMembers.forEach((m) =>
140 domToHtml.putIfAbsent(m, () => new Set()).add(htmlMember)); 119 domToHtml.putIfAbsent(m, () => new Set()).add(htmlMember));
141 } 120 }
142 121
143 /** 122 /**
144 * Returns the `dart:html` [Type] that corresponds to [implType] from
145 * `dart:html`, or `null` if there is no such correspondence.
146 */
147 Type htmlImplToHtmlType(Type implType) {
148 if (implType == null || implType.isTop || implType.interfaces.isEmpty() ||
149 implType.interfaces[0].library.name != 'html') {
150 return null;
151 }
152
153 return implType.interfaces[0];
154 }
155
156 /**
157 * Returns the `dart:html` [Member] that corresponds to [implMember] from
158 * `dart:html`, or `null` if there is no such correspondence.
159 */
160 Member htmlImplToHtmlMember(Member implMember) {
161 var htmlType = htmlImplToHtmlType(implMember.declaringType);
162 if (htmlType == null) return null;
163
164 if (implMember.isConstructor || implMember.isFactory) {
165 var constructor = htmlType.constructors[implMember.name];
166 if (constructor != null) return constructor;
167
168 // Look for a factory constructor whose type and name matches the member.
169 return htmlType.factories.getFactoriesFor(implMember.name)[
170 implMember.constructorName];
171 }
172
173 final getter = implMember.name.startsWith('get:');
174 final setter = implMember.name.startsWith('set:');
175
176 if (getter || setter) {
177 // Use getMember to follow interface inheritance chains.
178 var htmlProperty = htmlType.getMember(implMember.name.substring(4));
179
180 if (htmlProperty == null) return null;
181
182 // If it's a straight field, use that directly.
183 if (htmlProperty.isField) return htmlProperty;
184
185 // Otherwise, it's a property, so use the appropriate getter or setter.
186 return getter ? htmlProperty.getter : htmlProperty.setter;
187 }
188
189 return htmlType.getMember(implMember.name);
190 }
191
192 /**
193 * Returns the `dart:dom` [Type]s that correspond to [htmlType] from 123 * Returns the `dart:dom` [Type]s that correspond to [htmlType] from
194 * `dart:html`. This can be the empty list if no correspondence is found. 124 * `dart:html`. This can be the empty list if no correspondence is found.
195 */ 125 */
196 List<Type> htmlToDomTypes(Type htmlType) { 126 List<Type> htmlToDomTypes(Type htmlType) {
197 if (htmlType.name == null) return []; 127 if (htmlType.name == null) return [];
198 final tags = _getTags(comments.find(htmlType.span)); 128 final tags = _getTags(comments.find(htmlType.span));
199 129
200 if (tags.containsKey('domName')) { 130 if (tags.containsKey('domName')) {
201 var domNames = map(tags['domName'].split(','), (s) => s.trim()); 131 var domNames = map(tags['domName'].split(','), (s) => s.trim());
202 if (domNames.length == 1 && domNames[0] == 'none') return []; 132 if (domNames.length == 1 && domNames[0] == 'none') return [];
203 return map(domNames, (domName) { 133 return map(domNames, (domName) {
204 // DOMWindow is Chrome-specific, so we don't use it in our annotations.
205 if (domName == 'Window') domName = 'DOMWindow';
206 final domType = dom.types[domName]; 134 final domType = dom.types[domName];
207 if (domType == null) print('Warning: no dart:dom type named $domName'); 135 if (domType == null) print('Warning: no dart:dom type named $domName');
208 return domType; 136 return domType;
209 }); 137 });
210 } else {
211 if (!htmlType.name.endsWith('WrappingImplementation')) return [];
212 final domName = htmlType.name.replaceFirst('WrappingImplementation', '');
213 var domType = dom.types[domName];
214 if (domType == null && domName.endsWith('Element')) {
215 domType = dom.types['HTML$domName'];
216 }
217 if (domType == null) domType = dom.types['WebKit$domName'];
218 if (domType == null) {
219 print('Warning: no dart:dom type matches dart:html ' +
220 htmlType.name);
221 return [];
222 }
223 return [domType];
224 } 138 }
139 return <Type>[];
225 } 140 }
226 141
227 /** 142 /**
228 * Returns the `dart:dom` [Member]s that correspond to [htmlMember] from 143 * Returns the `dart:dom` [Member]s that correspond to [htmlMember] from
229 * `dart:html`. This can be the empty set if no correspondence is found. 144 * `dart:html`. This can be the empty set if no correspondence is found.
230 * [domTypes] are the `dart:dom` [Type]s that correspond to [implMember]'s 145 * [domTypes] are the `dart:dom` [Type]s that correspond to [implMember]'s
231 * defining [Type]. 146 * defining [Type].
232 */ 147 */
233 Set<Member> htmlToDomMembers(Member htmlMember, List<Type> domTypes) { 148 Set<Member> htmlToDomMembers(Member htmlMember, List<Type> domTypes) {
234 if (htmlMember.isPrivate || htmlMember is! MethodMember) return new Set(); 149 if (htmlMember.isPrivate) return new Set();
235 final tags = _getTags(comments.find(htmlMember.span)); 150 final tags = _getTags(comments.find(htmlMember.span));
236 if (tags.containsKey('domName')) { 151 if (tags.containsKey('domName')) {
237 final domNames = map(tags['domName'].split(','), (s) => s.trim()); 152 final domNames = map(tags['domName'].split(','), (s) => s.trim());
238 if (domNames.length == 1 && domNames[0] == 'none') return new Set(); 153 if (domNames.length == 1 && domNames[0] == 'none') return new Set();
239 final members = new Set(); 154 final members = new Set();
240 domNames.forEach((name) { 155 domNames.forEach((name) {
241 var nameMembers = _membersFromName(name, domTypes); 156 var nameMembers = _membersFromName(name, domTypes);
242 if (nameMembers.isEmpty()) { 157 if (nameMembers.isEmpty()) {
243 if (name.contains('.')) { 158 if (name.contains('.')) {
244 print('Warning: no member $name'); 159 print('Warning: no member $name');
245 } else { 160 } else {
246 final options = Strings.join( 161 final options = Strings.join(
247 map(domTypes, (t) => "${t.name}.$name"), ' or '); 162 map(domTypes, (t) => "${t.name}.$name"), ' or ');
248 print('Warning: no member $options'); 163 print('Warning: no member $options');
249 } 164 }
250 } 165 }
251 members.addAll(nameMembers); 166 members.addAll(nameMembers);
252 }); 167 });
253 return members; 168 return members;
254 } 169 }
255 170
256 if (domTypes.isEmpty() || htmlMember.definition == null) return new Set(); 171 return new Set();
257 if (htmlMember.name == 'get:on') {
258 final members = _membersFromName('addEventListener', domTypes);
259 members.addAll(_membersFromName('dispatchEvent', domTypes));
260 members.addAll(_membersFromName('removeEventListener', domTypes));
261 return members;
262 }
263
264 if (htmlMember.isFactory && htmlMember.name == '' &&
265 domTypes.length == 1 && domTypes[0].name.endsWith('Event')) {
266 return _membersFromName('init${domTypes[0].name}', domTypes);
267 }
268
269 return _getDomMembers(htmlMember.definition.body, domTypes);
270 } 172 }
271 173
272 /** 174 /**
273 * Returns the `dart:dom` [Member]s that are indicated by [name]. [name] can 175 * Returns the `dart:dom` [Member]s that are indicated by [name]. [name] can
274 * be either an unqualified member name (e.g. `createElement`), in which case 176 * be either an unqualified member name (e.g. `createElement`), in which case
275 * it's treated as the name of a member of one of [defaultTypes], or a 177 * it's treated as the name of a member of one of [defaultTypes], or a
276 * fully-qualified member name (e.g. `Document.createElement`), in which case 178 * fully-qualified member name (e.g. `Document.createElement`), in which case
277 * it's looked up in `dart:dom` and [defaultTypes] is ignored. 179 * it's looked up in `dart:dom` and [defaultTypes] is ignored.
278 */ 180 */
279 Set<Member> _membersFromName(String name, List<Type> defaultTypes) { 181 Set<Member> _membersFromName(String name, List<Type> defaultTypes) {
280 if (!name.contains('.', 0)) { 182 if (!name.contains('.', 0)) {
281 if (defaultTypes.isEmpty()) { 183 if (defaultTypes.isEmpty()) {
282 print('Warning: no default type for ${name}'); 184 print('Warning: no default type for ${name}');
283 return new Set(); 185 return new Set();
284 } 186 }
285 final members = new Set<Member>(); 187 final members = new Set<Member>();
286 defaultTypes.forEach((t) { 188 defaultTypes.forEach((t) {
287 if (t.members.containsKey(name)) members.add(t.members[name]); 189 if (t.members.containsKey(name)) members.add(t.members[name]);
288 }); 190 });
289 return members; 191 return members;
290 } 192 }
291 193
292 final splitName = name.split('.'); 194 final splitName = name.split('.');
293 if (splitName.length != 2) { 195 if (splitName.length != 2) {
294 print('Warning: invalid member name ${name}'); 196 print('Warning: invalid member name ${name}');
295 return new Set(); 197 return new Set();
296 } 198 }
297 199
298 var typeName = splitName[0]; 200 var typeName = splitName[0];
299 if (typeName == 'Window') typeName = 'DOMWindow';
300 201
301 final type = dom.types[typeName]; 202 final type = dom.types[typeName];
302 if (type == null) return new Set(); 203 if (type == null) return new Set();
303 204
304 final member = type.members[splitName[1]]; 205 final member = type.members[splitName[1]];
305 if (member == null) return new Set(); 206 if (member == null) return new Set();
306 207
307 return new Set.from([member]); 208 return new Set.from([member]);
308 } 209 }
309 210
310 /** 211 /**
311 * Returns the `dart:dom` [Member]s that are referred to in [stmt]. This only
312 * extracts references from relatively simple statements; methods containing
313 * more complex wrappers should be manually annotated with `@domName`.
314 *
315 * [domTypes] are the `dart:dom` [Type]s that correspond to the current
316 * [Member]'s defining [Type].
317 */
318 Set<Member> _getDomMembers(Statement stmt, List<Type> domTypes) {
319 if (stmt is BlockStatement) {
320 final body = stmt.body.filter((s) => !_ignorableStatement(s));
321 if (body.length != 1) return new Set();
322 return _getDomMembers(stmt.body[0], domTypes);
323 } else if (stmt is ReturnStatement) {
324 return _domMembersFromExpression(stmt.value, domTypes);
325 } else if (stmt is ExpressionStatement) {
326 return _domMembersFromExpression(stmt.body, domTypes);
327 } else if (stmt is TryStatement) {
328 return _getDomMembers(stmt.body, domTypes);
329 } else if (stmt is IfStatement) {
330 final members = _getDomMembers(stmt.trueBranch, domTypes);
331 members.addAll(_getDomMembers(stmt.falseBranch, domTypes));
332 return members;
333 } else {
334 return new Set();
335 }
336 }
337
338 /**
339 * Whether [stmt] can be ignored for the purpose of determining the DOM name
340 * of the enclosing method. The Webkit-to-Dart conversion process leaves
341 * behind various `throw`s and `return`s that we want to ignore.
342 */
343 bool _ignorableStatement(Statement stmt) {
344 if (stmt is BlockStatement) {
345 return Collections.every(stmt.body, (s) => _ignorableStatement(s));
346 } else if (stmt is TryStatement) {
347 return _ignorableStatement(stmt.body);
348 } else if (stmt is IfStatement) {
349 return _ignorableStatement(stmt.trueBranch) &&
350 _ignorableStatement(stmt.falseBranch);
351 } else if (stmt is ReturnStatement) {
352 return stmt.value == null || stmt.value is ThisExpression;
353 } else {
354 return stmt is ThrowStatement;
355 }
356 }
357
358 /**
359 * Returns the `dart:dom` [Member]s that are referred to in [expr]. This only
360 * extracts references from relatively simple expressions; methods containing
361 * more complex wrappers should be manually annotated with `@domName`.
362 *
363 * [domTypes] are the `dart:dom` [Type]s that correspond to the current
364 * [Member]'s defining [Type].
365 */
366 Set<Member> _domMembersFromExpression(Expression expr, List<Type> domTypes) {
367 if (expr is BinaryExpression && expr.op.kind == TokenKind.ASSIGN) {
368 return _domMembersFromExpression(expr.x, domTypes);
369 } else if (expr is CallExpression) {
370 if (expr.target is DotExpression && expr.target.self is VarExpression &&
371 expr.target.self.name.name == 'LevelDom' &&
372 (expr.target.name.name.startsWith('wrap') ||
373 expr.target.name.name == 'unwrap')) {
374 return _domMembersFromExpression(expr.arguments[0].value, domTypes);
375 }
376 return _domMembersFromExpression(expr.target, domTypes);
377 } else if (expr is DotExpression) {
378 if (expr.self is NewExpression && expr.name.name == '_wrap' &&
379 expr.self.arguments.length == 1) {
380 return _domMembersFromExpression(expr.self.arguments[0].value,
381 domTypes);
382 } else if (expr.self is VarExpression && expr.self.name.name == '_ptr') {
383 return _membersFromName(expr.name.name, domTypes);
384 }
385 final bases = _domMembersFromExpression(expr.self, domTypes);
386 return new Set.from(map(bases, (base) {
387 if (base == null || base.returnType == null) return null;
388 return base.returnType.members[expr.name.name];
389 }).filter((m) => m != null));
390 } else if (expr is NewExpression && expr.arguments.length == 1) {
391 return _domMembersFromExpression(expr.arguments[0].value, domTypes);
392 } else {
393 return new Set();
394 }
395 }
396
397 /**
398 * Extracts a [Map] from tag names to values from [comment], which is parsed 212 * Extracts a [Map] from tag names to values from [comment], which is parsed
399 * from a Dart source file via dartdoc. Tags are of the form `@NAME VALUE`, 213 * from a Dart source file via dartdoc. Tags are of the form `@NAME VALUE`,
400 * where `NAME` is alphabetic and `VALUE` can contain any character other than 214 * where `NAME` is alphabetic and `VALUE` can contain any character other than
401 * `;`. Multiple tags can be separated by semicolons. 215 * `;`. Multiple tags can be separated by semicolons.
402 * 216 *
403 * At time of writing, the only tag that's used is `@domName`. 217 * At time of writing, the only tag that's used is `@domName`.
404 */ 218 */
405 Map<String, String> _getTags(String comment) { 219 Map<String, String> _getTags(String comment) {
406 if (comment == null) return const <String>{}; 220 if (comment == null) return const <String>{};
407 final re = new RegExp("@([a-zA-Z]+) ([^;]+)(?:;|\$)"); 221 final re = const RegExp("@([a-zA-Z]+) ([^;]+)(?:;|\$)");
408 final tags = <String>{}; 222 final tags = <String>{};
409 for (var m in re.allMatches(comment.trim())) { 223 for (var m in re.allMatches(comment.trim())) {
410 tags[m[1]] = m[2]; 224 tags[m[1]] = m[2];
411 } 225 }
412 return tags; 226 return tags;
413 } 227 }
414 } 228 }
OLDNEW
« no previous file with comments | « utils/apidoc/apidoc.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698