| 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 |