OLD | NEW |
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:io'; | 11 import 'dart:io'; |
| 12 import 'dart:async'; |
| 13 import '../../sdk/lib/html/html_common/metadata.dart'; |
12 | 14 |
13 // TODO(rnystrom): Use "package:" URL (#4968). | 15 // TODO(rnystrom): Use "package:" URL (#4968). |
14 import '../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart'; | 16 import '../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart'; |
15 import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart'; | 17 import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart'; |
16 import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dar
t'; | 18 import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dar
t'; |
17 | 19 |
18 // TODO(amouravski): There is currently magic that looks at dart:* libraries | 20 // TODO(amouravski): There is currently magic that looks at dart:* libraries |
19 // rather than the declared library names. This changed due to recent syntax | 21 // rather than the declared library names. This changed due to recent syntax |
20 // changes. We should only need to look at the library 'html'. | 22 // changes. We should only need to look at the library 'html'. |
21 const List<String> HTML_LIBRARY_NAMES = const [ | 23 const List<String> HTML_LIBRARY_NAMES = const [ |
22 'dart:html', | 24 'dart:html', |
23 'dart:svg', | 25 'dart:svg', |
24 'dart:web_audio']; | 26 'dart:web_audio']; |
25 const List<String> HTML_DECLARED_NAMES = const [ | 27 const List<String> HTML_DECLARED_NAMES = const [ |
26 'html', | 28 'html', |
27 'svg', | 29 'svg', |
28 'web_audio']; | 30 'web_audio']; |
29 | 31 |
30 /** | 32 /** |
31 * A class for computing a many-to-many mapping between the types and | 33 * A class for computing a many-to-many mapping between the types and |
32 * members in `dart:html` and the MDN DOM types. This mapping is | 34 * members in `dart:html` and the MDN DOM types. This mapping is |
33 * based on two indicators: | 35 * based on two indicators: |
34 * | 36 * |
35 * 1. Auto-detected wrappers. Most `dart:html` types correspond | 37 * 1. Auto-detected wrappers. Most `dart:html` types correspond |
36 * straightforwardly to a single `@domName` type, and | 38 * straightforwardly to a single `@DomName` type, and |
37 * have the same name. In addition, most `dart:html` methods | 39 * have the same name. In addition, most `dart:html` methods |
38 * just call a single `@domName` method. This class | 40 * just call a single `@DomName` method. This class |
39 * detects these simple correspondences automatically. | 41 * detects these simple correspondences automatically. |
40 * | 42 * |
41 * 2. Manual annotations. When it's not clear which | 43 * 2. Manual annotations. When it's not clear which |
42 * `@domName` items a given `dart:html` item | 44 * `@DomName` items a given `dart:html` item |
43 * corresponds to, the `dart:html` item can be annotated in the | 45 * corresponds to, the `dart:html` item can be annotated in the |
44 * documentation comments using the `@domName` annotation. | 46 * documentation comments using the `@DomName` annotation. |
45 * | 47 * |
46 * The `@domName` annotations for types and members are of the form | 48 * The `@DomName` annotations for types and members are of the form |
47 * `@domName NAME(, NAME)*`, where the `NAME`s refer to the | 49 * `@DomName NAME(, NAME)*`, where the `NAME`s refer to the |
48 * `@domName` types/members that correspond to the | 50 * `@DomName` types/members that correspond to the |
49 * annotated `dart:html` type/member. `NAME`s on member annotations | 51 * annotated `dart:html` type/member. `NAME`s on member annotations |
50 * can refer to either fully-qualified member names (e.g. | 52 * can refer to either fully-qualified member names (e.g. |
51 * `Document.createElement`) or unqualified member names | 53 * `Document.createElement`) or unqualified member names |
52 * (e.g. `createElement`). Unqualified member names are assumed to | 54 * (e.g. `createElement`). Unqualified member names are assumed to |
53 * refer to members of one of the corresponding `@domName` | 55 * refer to members of one of the corresponding `@DomName` |
54 * types. | 56 * types. |
55 */ | 57 */ |
56 class HtmlDiff { | 58 class HtmlDiff { |
57 /** | 59 /** |
58 * A map from `dart:html` members to the corresponding fully qualified | 60 * A map from `dart:html` members to the corresponding fully qualified |
59 * `@domName` member(s). | 61 * `@DomName` member(s). |
60 */ | 62 */ |
61 final Map<String, Set<String>> htmlToDom; | 63 final Map<String, Set<String>> htmlToDom; |
62 | 64 |
63 /** A map from `dart:html` types to corresponding `@domName` types. */ | 65 /** A map from `dart:html` types to corresponding `@DomName` types. */ |
64 final Map<String, Set<String>> htmlTypesToDom; | 66 final Map<String, Set<String>> htmlTypesToDom; |
65 | 67 |
66 final CommentMap comments; | 68 final CommentMap comments; |
67 | 69 |
68 /** If true, then print warning messages. */ | 70 /** If true, then print warning messages. */ |
69 final bool _printWarnings; | 71 final bool _printWarnings; |
70 | 72 |
71 static Compilation _compilation; | 73 static Compilation _compilation; |
72 static MirrorSystem _mirrors; | 74 static MirrorSystem _mirrors; |
73 static LibraryMirror dom; | 75 static LibraryMirror dom; |
(...skipping 17 matching lines...) Expand all Loading... |
91 htmlTypesToDom = new Map<String, Set<String>>(), | 93 htmlTypesToDom = new Map<String, Set<String>>(), |
92 comments = new CommentMap(); | 94 comments = new CommentMap(); |
93 | 95 |
94 void warn(String s) { | 96 void warn(String s) { |
95 if (_printWarnings) { | 97 if (_printWarnings) { |
96 print('Warning: $s'); | 98 print('Warning: $s'); |
97 } | 99 } |
98 } | 100 } |
99 | 101 |
100 /** | 102 /** |
101 * Computes the `@domName` to `dart:html` mapping, and | 103 * Computes the `@DomName` to `dart:html` mapping, and |
102 * places it in [htmlToDom] and [htmlTypesToDom]. Before this is run, dart2js | 104 * places it in [htmlToDom] and [htmlTypesToDom]. Before this is run, dart2js |
103 * should be initialized (via [parseOptions] and [initializeWorld]) and | 105 * should be initialized (via [parseOptions] and [initializeWorld]) and |
104 * [HtmlDiff.initialize] should be called. | 106 * [HtmlDiff.initialize] should be called. |
105 */ | 107 */ |
106 void run() { | 108 void run() { |
107 for (var libraryName in HTML_DECLARED_NAMES) { | 109 for (var libraryName in HTML_DECLARED_NAMES) { |
108 var library = _mirrors.libraries[libraryName]; | 110 var library = _mirrors.libraries[libraryName]; |
109 if (library == null) { | 111 if (library == null) { |
110 warn('Could not find $libraryName'); | 112 warn('Could not find $libraryName'); |
111 return; | 113 return; |
112 } | 114 } |
113 for (ClassMirror type in library.classes.values) { | 115 for (ClassMirror type in library.classes.values) { |
114 final domTypes = htmlToDomTypes(type); | 116 final domTypes = htmlToDomTypes(type); |
115 if (domTypes.isEmpty) continue; | 117 if (domTypes.isEmpty) continue; |
116 | 118 |
117 htmlTypesToDom.putIfAbsent(type.qualifiedName, | 119 htmlTypesToDom.putIfAbsent(type.qualifiedName, |
118 () => new Set()).addAll(domTypes); | 120 () => new Set()).addAll(domTypes); |
119 | 121 |
120 type.members.forEach( | 122 type.members.forEach( |
121 (_, m) => _addMemberDiff(m, domTypes, library.simpleName)); | 123 (_, m) => _addMemberDiff(m, domTypes, library.simpleName)); |
122 } | 124 } |
123 } | 125 } |
124 } | 126 } |
125 | 127 |
126 /** | 128 /** |
127 * Records the `@domName` to `dart:html` mapping for | 129 * Records the `@DomName` to `dart:html` mapping for |
128 * [htmlMember] (from `dart:html`). [domTypes] are the | 130 * [htmlMember] (from `dart:html`). [domTypes] are the |
129 * `@domName` type values that correspond to [htmlMember]'s | 131 * `@DomName` type values that correspond to [htmlMember]'s |
130 * defining type. | 132 * defining type. |
131 */ | 133 */ |
132 void _addMemberDiff(MemberMirror htmlMember, List<String> domTypes, | 134 void _addMemberDiff(MemberMirror htmlMember, List<String> domTypes, |
133 String libraryName) { | 135 String libraryName) { |
134 var domMembers = htmlToDomMembers(htmlMember, domTypes); | 136 var domMembers = htmlToDomMembers(htmlMember, domTypes); |
135 if (htmlMember == null && !domMembers.isEmpty) { | 137 if (htmlMember == null && !domMembers.isEmpty) { |
136 warn('$libraryName member ' | 138 warn('$libraryName member ' |
137 '${htmlMember.owner.simpleName}.' | 139 '${htmlMember.owner.simpleName}.' |
138 '${htmlMember.simpleName} has no corresponding ' | 140 '${htmlMember.simpleName} has no corresponding ' |
139 '$libraryName member.'); | 141 '$libraryName member.'); |
140 } | 142 } |
141 | 143 |
142 if (htmlMember == null) return; | 144 if (htmlMember == null) return; |
143 if (!domMembers.isEmpty) { | 145 if (!domMembers.isEmpty) { |
144 htmlToDom[htmlMember.qualifiedName] = domMembers; | 146 htmlToDom[htmlMember.qualifiedName] = domMembers; |
145 } | 147 } |
146 } | 148 } |
147 | 149 |
148 /** | 150 /** |
149 * Returns the `@domName` type values that correspond to | 151 * Returns the `@DomName` type values that correspond to |
150 * [htmlType] from `dart:html`. This can be the empty list if no | 152 * [htmlType] from `dart:html`. This can be the empty list if no |
151 * correspondence is found. | 153 * correspondence is found. |
152 */ | 154 */ |
153 List<String> htmlToDomTypes(ClassMirror htmlType) { | 155 List<String> htmlToDomTypes(ClassMirror htmlType) { |
154 if (htmlType.simpleName == null) return []; | 156 if (htmlType.simpleName == null) return <String>[]; |
155 final tags = _getTags(comments.find(htmlType.location)); | 157 |
156 if (tags.containsKey('domName')) { | 158 final domNameMetadata = _findMetadata(htmlType.metadata, 'DomName'); |
| 159 if (domNameMetadata != null) { |
157 var domNames = <String>[]; | 160 var domNames = <String>[]; |
158 for (var s in tags['domName'].split(',')) { | 161 var tags = deprecatedFutureValue(domNameMetadata.getField('name')); |
| 162 for (var s in tags.reflectee.split(',')) { |
159 domNames.add(s.trim()); | 163 domNames.add(s.trim()); |
160 } | 164 } |
| 165 |
161 if (domNames.length == 1 && domNames[0] == 'none') return <String>[]; | 166 if (domNames.length == 1 && domNames[0] == 'none') return <String>[]; |
162 return domNames; | 167 return domNames; |
163 } | 168 } |
164 return <String>[]; | 169 return <String>[]; |
165 } | 170 } |
166 | 171 |
167 /** | 172 /** |
168 * Returns the `@domName` member values that correspond to | 173 * Returns the `@DomName` member values that correspond to |
169 * [htmlMember] from `dart:html`. This can be the empty set if no | 174 * [htmlMember] from `dart:html`. This can be the empty set if no |
170 * correspondence is found. [domTypes] are the | 175 * correspondence is found. [domTypes] are the |
171 * `@domName` type values that correspond to [htmlMember]'s | 176 * `@DomName` type values that correspond to [htmlMember]'s |
172 * defining type. | 177 * defining type. |
173 */ | 178 */ |
174 Set<String> htmlToDomMembers(MemberMirror htmlMember, List<String> domTypes) { | 179 Set<String> htmlToDomMembers(MemberMirror htmlMember, List<String> domTypes) { |
175 if (htmlMember.isPrivate) return new Set(); | 180 if (htmlMember.isPrivate) return new Set(); |
176 final tags = _getTags(comments.find(htmlMember.location)); | 181 |
177 if (tags.containsKey('domName')) { | 182 final domNameMetadata = _findMetadata(htmlMember.metadata, 'DomName'); |
| 183 if (domNameMetadata != null) { |
178 var domNames = <String>[]; | 184 var domNames = <String>[]; |
179 for (var s in tags['domName'].split(',')) { | 185 var tags = deprecatedFutureValue(domNameMetadata.getField('name')); |
| 186 for (var s in tags.reflectee.split(',')) { |
180 domNames.add(s.trim()); | 187 domNames.add(s.trim()); |
181 } | 188 } |
| 189 |
182 if (domNames.length == 1 && domNames[0] == 'none') return new Set(); | 190 if (domNames.length == 1 && domNames[0] == 'none') return new Set(); |
183 final members = new Set(); | 191 final members = new Set(); |
184 domNames.forEach((name) { | 192 domNames.forEach((name) { |
185 var nameMembers = _membersFromName(name, domTypes); | 193 var nameMembers = _membersFromName(name, domTypes); |
186 if (nameMembers.isEmpty) { | 194 if (nameMembers.isEmpty) { |
187 if (name.contains('.')) { | 195 if (name.contains('.')) { |
188 warn('no member $name'); | 196 warn('no member $name'); |
189 } else { | 197 } else { |
190 final options = <String>[]; | 198 final options = <String>[]; |
191 for (var t in domTypes) { | 199 for (var t in domTypes) { |
192 options.add('$t.$name'); | 200 options.add('$t.$name'); |
193 } | 201 } |
194 Strings.join(options, ' or '); | 202 Strings.join(options, ' or '); |
195 warn('no member $options'); | 203 warn('no member $options'); |
196 } | 204 } |
197 } | 205 } |
198 members.addAll(nameMembers); | 206 members.addAll(nameMembers); |
199 }); | 207 }); |
200 return members; | 208 return members; |
201 } | 209 } |
202 | 210 |
203 return new Set(); | 211 return new Set(); |
204 } | 212 } |
205 | 213 |
206 /** | 214 /** |
207 * Returns the `@domName` strings that are indicated by | 215 * Returns the `@DomName` strings that are indicated by |
208 * [name]. [name] can be either an unqualified member name | 216 * [name]. [name] can be either an unqualified member name |
209 * (e.g. `createElement`), in which case it's treated as the name of | 217 * (e.g. `createElement`), in which case it's treated as the name of |
210 * a member of one of [defaultTypes], or a fully-qualified member | 218 * a member of one of [defaultTypes], or a fully-qualified member |
211 * name (e.g. `Document.createElement`), in which case it's treated as a | 219 * name (e.g. `Document.createElement`), in which case it's treated as a |
212 * member of the @domName element (`Document` in this case). | 220 * member of the @DomName element (`Document` in this case). |
213 */ | 221 */ |
214 Set<String> _membersFromName(String name, List<String> defaultTypes) { | 222 Set<String> _membersFromName(String name, List<String> defaultTypes) { |
215 if (!name.contains('.', 0)) { | 223 if (!name.contains('.', 0)) { |
216 if (defaultTypes.isEmpty) { | 224 if (defaultTypes.isEmpty) { |
217 warn('no default type for $name'); | 225 warn('no default type for $name'); |
218 return new Set(); | 226 return new Set(); |
219 } | 227 } |
220 final members = new Set<String>(); | 228 final members = new Set<String>(); |
221 defaultTypes.forEach((t) { members.add('$t.$name'); }); | 229 defaultTypes.forEach((t) { members.add('$t.$name'); }); |
222 return members; | 230 return members; |
223 } | 231 } |
224 | 232 |
225 if (name.split('.').length != 2) { | 233 if (name.split('.').length != 2) { |
226 warn('invalid member name ${name}'); | 234 warn('invalid member name ${name}'); |
227 return new Set(); | 235 return new Set(); |
228 } | 236 } |
229 return new Set.from([name]); | 237 return new Set.from([name]); |
230 } | 238 } |
231 | 239 |
232 /** | |
233 * Extracts a [Map] from tag names to values from [comment], which is parsed | |
234 * from a Dart source file via dartdoc. Tags are of the form `@NAME VALUE`, | |
235 * where `NAME` is alphabetic and `VALUE` can contain any character other than | |
236 * `;`. Multiple tags can be separated by semicolons. | |
237 * | |
238 * At time of writing, the only tag that's used is `@domName`. | |
239 */ | |
240 Map<String, String> _getTags(String comment) { | |
241 if (comment == null) return const <String, String>{}; | |
242 final re = new RegExp("@([a-zA-Z]+) ([^;]+)(?:;|\$)"); | |
243 final tags = <String, String>{}; | |
244 for (var m in re.allMatches(comment.trim())) { | |
245 tags[m[1]] = m[2]; | |
246 } | |
247 return tags; | |
248 } | |
249 } | 240 } |
| 241 |
| 242 /// Returns the metadata for the given string or null if not found. |
| 243 InstanceMirror _findMetadata(List<InstanceMirror> metadataList, String find) { |
| 244 return metadataList.firstMatching( |
| 245 (metadata) => metadata.type.simpleName == find, |
| 246 orElse: () => null); |
| 247 } |
OLD | NEW |