OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * A script to assist in documenting the difference between the dart:html API | |
7 * and the old DOM API. | |
8 */ | |
9 library html_diff; | |
10 | |
11 import 'dart:async'; | |
12 | |
13 import 'lib/metadata.dart'; | |
14 | |
15 // TODO(rnystrom): Use "package:" URL (#4968). | |
16 import '../../pkg/compiler/lib/src/mirrors/analyze.dart'; | |
17 import '../../pkg/compiler/lib/src/mirrors/source_mirrors.dart'; | |
18 import '../../pkg/compiler/lib/src/mirrors/mirrors_util.dart'; | |
19 import '../../pkg/compiler/lib/src/source_file_provider.dart'; | |
20 | |
21 // TODO(amouravski): There is currently magic that looks at dart:* libraries | |
22 // rather than the declared library names. This changed due to recent syntax | |
23 // changes. We should only need to look at the library 'html'. | |
24 final List<Uri> HTML_LIBRARY_URIS = [ | |
25 new Uri(scheme: 'dart', path: 'html'), | |
26 new Uri(scheme: 'dart', path: 'indexed_db'), | |
27 new Uri(scheme: 'dart', path: 'svg'), | |
28 new Uri(scheme: 'dart', path: 'web_audio')]; | |
29 | |
30 /** | |
31 * 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 | |
33 * based on two indicators: | |
34 * | |
35 * 1. Auto-detected wrappers. Most `dart:html` types correspond | |
36 * straightforwardly to a single `@DomName` type, and | |
37 * have the same name. In addition, most `dart:html` methods | |
38 * just call a single `@DomName` method. This class | |
39 * detects these simple correspondences automatically. | |
40 * | |
41 * 2. Manual annotations. When it's not clear which | |
42 * `@DomName` items a given `dart:html` item | |
43 * corresponds to, the `dart:html` item can be annotated in the | |
44 * documentation comments using the `@DomName` annotation. | |
45 * | |
46 * The `@DomName` annotations for types and members are of the form | |
47 * `@DomName NAME(, NAME)*`, where the `NAME`s refer to the | |
48 * `@DomName` types/members that correspond to the | |
49 * annotated `dart:html` type/member. `NAME`s on member annotations | |
50 * can refer to either fully-qualified member names (e.g. | |
51 * `Document.createElement`) or unqualified member names | |
52 * (e.g. `createElement`). Unqualified member names are assumed to | |
53 * refer to members of one of the corresponding `@DomName` | |
54 * types. | |
55 */ | |
56 class HtmlDiff { | |
57 /** | |
58 * A map from `dart:html` members to the corresponding fully qualified | |
59 * `@DomName` member(s). | |
60 */ | |
61 final Map<String, Set<String>> htmlToDom; | |
62 | |
63 /** A map from `dart:html` types to corresponding `@DomName` types. */ | |
64 final Map<String, Set<String>> htmlTypesToDom; | |
65 | |
66 /** If true, then print warning messages. */ | |
67 final bool _printWarnings; | |
68 | |
69 static LibraryMirror dom; | |
70 | |
71 HtmlDiff({bool printWarnings: false}) : | |
72 _printWarnings = printWarnings, | |
73 htmlToDom = new Map<String, Set<String>>(), | |
74 htmlTypesToDom = new Map<String, Set<String>>(); | |
75 | |
76 void warn(String s) { | |
77 if (_printWarnings) { | |
78 print('Warning: $s'); | |
79 } | |
80 } | |
81 | |
82 /** | |
83 * Computes the `@DomName` to `dart:html` mapping, and | |
84 * places it in [htmlToDom] and [htmlTypesToDom]. Before this is run, dart2js | |
85 * should be initialized (via [parseOptions] and [initializeWorld]) and | |
86 * [HtmlDiff.initialize] should be called. | |
87 */ | |
88 Future run(Uri libraryRoot) { | |
89 var result = new Completer(); | |
90 var provider = new CompilerSourceFileProvider(); | |
91 var handler = new FormattingDiagnosticHandler(provider); | |
92 Future<MirrorSystem> analysis = analyze( | |
93 HTML_LIBRARY_URIS, libraryRoot, null, | |
94 provider.readStringFromUri, | |
95 handler.diagnosticHandler); | |
96 analysis.then((MirrorSystem mirrors) { | |
97 for (var libraryUri in HTML_LIBRARY_URIS) { | |
98 var library = mirrors.libraries[libraryUri]; | |
99 if (library == null) { | |
100 warn('Could not find $libraryUri'); | |
101 result.complete(false); | |
102 } | |
103 for (ClassMirror type in classesOf(library.declarations)) { | |
104 final domTypes = htmlToDomTypes(type); | |
105 if (domTypes.isEmpty) continue; | |
106 | |
107 htmlTypesToDom.putIfAbsent(qualifiedNameOf(type), | |
108 () => new Set()).addAll(domTypes); | |
109 | |
110 membersOf(type.declarations).forEach( | |
111 (m) => _addMemberDiff(m, domTypes, nameOf(library))); | |
112 } | |
113 } | |
114 result.complete(true); | |
115 }); | |
116 return result.future; | |
117 } | |
118 | |
119 /** | |
120 * Records the `@DomName` to `dart:html` mapping for | |
121 * [htmlMember] (from `dart:html`). [domTypes] are the | |
122 * `@DomName` type values that correspond to [htmlMember]'s | |
123 * defining type. | |
124 */ | |
125 void _addMemberDiff(DeclarationMirror htmlMember, List<String> domTypes, | |
126 String libraryName) { | |
127 var domMembers = htmlToDomMembers(htmlMember, domTypes); | |
128 if (htmlMember == null && !domMembers.isEmpty) { | |
129 warn('$libraryName member ' | |
130 '${htmlMember.owner.simpleName}.' | |
131 '${htmlMember.simpleName} has no corresponding ' | |
132 '$libraryName member.'); | |
133 } | |
134 | |
135 if (htmlMember == null) return; | |
136 if (!domMembers.isEmpty) { | |
137 htmlToDom[qualifiedNameOf(htmlMember)] = domMembers; | |
138 } | |
139 } | |
140 | |
141 /** | |
142 * Returns the `@DomName` type values that correspond to | |
143 * [htmlType] from `dart:html`. This can be the empty list if no | |
144 * correspondence is found. | |
145 */ | |
146 List<String> htmlToDomTypes(ClassMirror htmlType) { | |
147 if (htmlType.simpleName == null) return <String>[]; | |
148 | |
149 final domNameMetadata = findMetadata(htmlType.metadata, 'DomName'); | |
150 if (domNameMetadata != null) { | |
151 var domNames = <String>[]; | |
152 var names = domNameMetadata.getField(symbolOf('name')); | |
153 for (var s in names.reflectee.split(',')) { | |
154 domNames.add(s.trim()); | |
155 } | |
156 | |
157 if (domNames.length == 1 && domNames[0] == 'none') return <String>[]; | |
158 return domNames; | |
159 } | |
160 return <String>[]; | |
161 } | |
162 | |
163 /** | |
164 * Returns the `@DomName` member values that correspond to | |
165 * [htmlMember] from `dart:html`. This can be the empty set if no | |
166 * correspondence is found. [domTypes] are the | |
167 * `@DomName` type values that correspond to [htmlMember]'s | |
168 * defining type. | |
169 */ | |
170 Set<String> htmlToDomMembers(DeclarationMirror htmlMember, | |
171 List<String> domTypes) { | |
172 if (htmlMember.isPrivate) return new Set(); | |
173 | |
174 final domNameMetadata = findMetadata(htmlMember.metadata, 'DomName'); | |
175 if (domNameMetadata != null) { | |
176 var domNames = <String>[]; | |
177 var names = domNameMetadata.getField(symbolOf('name')); | |
178 for (var s in names.reflectee.split(',')) { | |
179 domNames.add(s.trim()); | |
180 } | |
181 | |
182 if (domNames.length == 1 && domNames[0] == 'none') return new Set(); | |
183 final members = new Set(); | |
184 domNames.forEach((name) { | |
185 var nameMembers = _membersFromName(name, domTypes); | |
186 if (nameMembers.isEmpty) { | |
187 if (name.contains('.')) { | |
188 warn('no member $name'); | |
189 } else { | |
190 final options = <String>[]; | |
191 for (var t in domTypes) { | |
192 options.add('$t.$name'); | |
193 } | |
194 options.join(' or '); | |
195 warn('no member $options'); | |
196 } | |
197 } | |
198 members.addAll(nameMembers); | |
199 }); | |
200 return members; | |
201 } | |
202 | |
203 return new Set(); | |
204 } | |
205 | |
206 /** | |
207 * Returns the `@DomName` strings that are indicated by | |
208 * [name]. [name] can be either an unqualified member name | |
209 * (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 | |
211 * name (e.g. `Document.createElement`), in which case it's treated as a | |
212 * member of the @DomName element (`Document` in this case). | |
213 */ | |
214 Set<String> _membersFromName(String name, List<String> defaultTypes) { | |
215 if (!name.contains('.', 0)) { | |
216 if (defaultTypes.isEmpty) { | |
217 warn('no default type for $name'); | |
218 return new Set(); | |
219 } | |
220 final members = new Set<String>(); | |
221 defaultTypes.forEach((t) { members.add('$t.$name'); }); | |
222 return members; | |
223 } | |
224 | |
225 if (name.split('.').length != 2) { | |
226 warn('invalid member name ${name}'); | |
227 return new Set(); | |
228 } | |
229 return new Set.from([name]); | |
230 } | |
231 } | |
OLD | NEW |