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

Side by Side Diff: pkg/observe/lib/transform.dart

Issue 25439002: fix observe transform to preserve metadata (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: use isPrivateName Created 7 years, 2 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 | « no previous file | pkg/observe/test/transform_test.dart » ('j') | 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) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, 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 * Code transform for @observable. The core transformation is relatively 6 * Code transform for @observable. The core transformation is relatively
7 * straightforward, and essentially like an editor refactoring. 7 * straightforward, and essentially like an editor refactoring.
8 */ 8 */
9 library observe.transform; 9 library observe.transform;
10 10
(...skipping 16 matching lines...) Expand all
27 */ 27 */
28 class ObservableTransformer extends Transformer { 28 class ObservableTransformer extends Transformer {
29 29
30 Future<bool> isPrimary(Asset input) { 30 Future<bool> isPrimary(Asset input) {
31 if (input.id.extension != '.dart') return new Future.value(false); 31 if (input.id.extension != '.dart') return new Future.value(false);
32 // Note: technically we should parse the file to find accurately the 32 // Note: technically we should parse the file to find accurately the
33 // observable annotation, but that seems expensive. It would require almost 33 // observable annotation, but that seems expensive. It would require almost
34 // as much work as applying the transform. We rather have some false 34 // as much work as applying the transform. We rather have some false
35 // positives here, and then generate no outputs when we apply this 35 // positives here, and then generate no outputs when we apply this
36 // transform. 36 // transform.
37 return input.readAsString().then((c) => c.contains("@observable")); 37 return input.readAsString().then(
38 (c) => c.contains("@observable") || c.contains("@published"));
38 } 39 }
39 40
40 Future apply(Transform transform) { 41 Future apply(Transform transform) {
41 return transform.primaryInput.readAsString().then((content) { 42 return transform.primaryInput.readAsString().then((content) {
42 var id = transform.primaryInput.id; 43 var id = transform.primaryInput.id;
43 // TODO(sigmund): improve how we compute this url 44 // TODO(sigmund): improve how we compute this url
44 var url = id.path.startsWith('lib/') 45 var url = id.path.startsWith('lib/')
45 ? 'package:${id.package}/${id.path.substring(4)}' : id.path; 46 ? 'package:${id.package}/${id.path.substring(4)}' : id.path;
46 var sourceFile = new SourceFile.text(url, content); 47 var sourceFile = new SourceFile.text(url, content);
47 var transaction = _transformCompilationUnit( 48 var transaction = _transformCompilationUnit(
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 return parser.parseCompilationUnit(token); 96 return parser.parseCompilationUnit(token);
96 } 97 }
97 98
98 class _ErrorCollector extends AnalysisErrorListener { 99 class _ErrorCollector extends AnalysisErrorListener {
99 final errors = <AnalysisError>[]; 100 final errors = <AnalysisError>[];
100 onError(error) => errors.add(error); 101 onError(error) => errors.add(error);
101 } 102 }
102 103
103 _getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end); 104 _getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end);
104 105
105 /** True if the node has the `@observable` annotation. */ 106 /** True if the node has the `@observable` or `@published` annotation. */
106 bool _hasObservable(AnnotatedNode node) => _hasAnnotation(node, 'observable'); 107 // TODO(jmesserly): it is not good to be hard coding these. We should do a
108 // resolve and do a proper ObservableProperty subtype check. However resolve
109 // is very expensive in analyzer_experimental, so it isn't feasible yet.
110 bool _hasObservable(AnnotatedNode node) =>
111 _hasAnnotation(node, 'observable') || _hasAnnotation(node, 'published');
107 112
108 bool _hasAnnotation(AnnotatedNode node, String name) { 113 bool _hasAnnotation(AnnotatedNode node, String name) {
109 // TODO(jmesserly): this isn't correct if the annotation has been imported 114 // TODO(jmesserly): this isn't correct if the annotation has been imported
110 // with a prefix, or cases like that. We should technically be resolving, but 115 // with a prefix, or cases like that. We should technically be resolving, but
111 // that is expensive. 116 // that is expensive.
112 return node.metadata.any((m) => m.name.name == name && 117 return node.metadata.any((m) => m.name.name == name &&
113 m.constructorName == null && m.arguments == null); 118 m.constructorName == null && m.arguments == null);
114 } 119 }
115 120
116 void _transformClass(ClassDeclaration cls, TextEditTransaction code, 121 void _transformClass(ClassDeclaration cls, TextEditTransaction code,
(...skipping 10 matching lines...) Expand all
127 // syntactic understanding of the code), we only report warnings that are 132 // syntactic understanding of the code), we only report warnings that are
128 // known to be true. 133 // known to be true.
129 var declaresObservable = false; 134 var declaresObservable = false;
130 if (cls.extendsClause != null) { 135 if (cls.extendsClause != null) {
131 var id = _getSimpleIdentifier(cls.extendsClause.superclass.name); 136 var id = _getSimpleIdentifier(cls.extendsClause.superclass.name);
132 if (id.name == 'ObservableBase') { 137 if (id.name == 'ObservableBase') {
133 code.edit(id.offset, id.end, 'ChangeNotifierBase'); 138 code.edit(id.offset, id.end, 'ChangeNotifierBase');
134 declaresObservable = true; 139 declaresObservable = true;
135 } else if (id.name == 'ChangeNotifierBase') { 140 } else if (id.name == 'ChangeNotifierBase') {
136 declaresObservable = true; 141 declaresObservable = true;
137 } else if (id.name != 'PolymerElement' && id.name != 'CustomElement' 142 } else if (id.name != 'HtmlElement' && id.name != 'CustomElement'
138 && id.name != 'Object') { 143 && id.name != 'Object') {
139 // TODO(sigmund): this is conservative, consider using type-resolution to 144 // TODO(sigmund): this is conservative, consider using type-resolution to
140 // improve this check. 145 // improve this check.
141 declaresObservable = true; 146 declaresObservable = true;
142 } 147 }
143 } 148 }
144 149
145 if (cls.withClause != null) { 150 if (cls.withClause != null) {
146 for (var type in cls.withClause.mixinTypes) { 151 for (var type in cls.withClause.mixinTypes) {
147 var id = _getSimpleIdentifier(type.name); 152 var id = _getSimpleIdentifier(type.name);
(...skipping 28 matching lines...) Expand all
176 if (member.isStatic) { 181 if (member.isStatic) {
177 if (_hasObservable(member)){ 182 if (_hasObservable(member)){
178 logger.warning('Static fields can no longer be observable. ' 183 logger.warning('Static fields can no longer be observable. '
179 'Observable fields should be put in an observable objects.', 184 'Observable fields should be put in an observable objects.',
180 _getSpan(file, member)); 185 _getSpan(file, member));
181 } 186 }
182 continue; 187 continue;
183 } 188 }
184 if (_hasObservable(member)) { 189 if (_hasObservable(member)) {
185 if (!declaresObservable) { 190 if (!declaresObservable) {
186 logger.warning('Observable fields should be put in an observable' 191 logger.warning('Observable fields should be put in an observable '
187 ' objects. Please declare that this class extends from ' 192 'objects. Please declare that this class extends from '
188 'ObservableBase, includes ObservableMixin, or implements ' 193 'ObservableBase, includes ObservableMixin, or implements '
189 'Observable.', 194 'Observable.',
190 _getSpan(file, member)); 195 _getSpan(file, member));
191
192 } 196 }
193 _transformFields(member.fields, code, member.offset, member.end); 197 _transformFields(file, member, code, logger);
194 198
195 var names = member.fields.variables.map((v) => v.name.name); 199 var names = member.fields.variables.map((v) => v.name.name);
196 200
197 getters.addAll(names); 201 getters.addAll(names);
198 if (!_isReadOnly(member.fields)) { 202 if (!_isReadOnly(member.fields)) {
199 setters.addAll(names); 203 setters.addAll(names);
200 instanceFields.addAll(names); 204 instanceFields.addAll(names);
201 } 205 }
202 } 206 }
203 } 207 }
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
282 } 286 }
283 287
284 code.edit(offset, offset, inserted); 288 code.edit(offset, offset, inserted);
285 } 289 }
286 290
287 bool _isReadOnly(VariableDeclarationList fields) { 291 bool _isReadOnly(VariableDeclarationList fields) {
288 return _hasKeyword(fields.keyword, Keyword.CONST) || 292 return _hasKeyword(fields.keyword, Keyword.CONST) ||
289 _hasKeyword(fields.keyword, Keyword.FINAL); 293 _hasKeyword(fields.keyword, Keyword.FINAL);
290 } 294 }
291 295
292 void _transformFields(VariableDeclarationList fields, TextEditTransaction code, 296 void _transformFields(SourceFile file, FieldDeclaration member,
293 int begin, int end) { 297 TextEditTransaction code, TransformLogger logger) {
294 298
299 final fields = member.fields;
295 if (_isReadOnly(fields)) return; 300 if (_isReadOnly(fields)) return;
296 301
297 var indent = guessIndent(code.original, begin); 302 // Private fields aren't supported:
298 var replace = new StringBuffer(); 303 for (var field in fields.variables) {
304 final name = field.name.name;
305 if (Identifier.isPrivateName(name)) {
306 logger.warning('Cannot make private field $name observable.',
307 _getSpan(file, field));
308 return;
309 }
310 }
299 311
300 // Unfortunately "var" doesn't work in all positions where type annotations 312 // Unfortunately "var" doesn't work in all positions where type annotations
301 // are allowed, such as "var get name". So we use "dynamic" instead. 313 // are allowed, such as "var get name". So we use "dynamic" instead.
302 var type = 'dynamic'; 314 var type = 'dynamic';
303 if (fields.type != null) { 315 if (fields.type != null) {
304 type = _getOriginalCode(code, fields.type); 316 type = _getOriginalCode(code, fields.type);
317 } else if (_hasKeyword(fields.keyword, Keyword.VAR)) {
318 // Replace 'var' with 'dynamic'
319 code.edit(fields.keyword.offset, fields.keyword.end, type);
305 } 320 }
306 321
307 for (var field in fields.variables) { 322 // Note: the replacements here are a bit subtle. It needs to support multiple
308 var initializer = ''; 323 // fields declared via the same @observable, as well as preserving newlines.
309 if (field.initializer != null) { 324 // (Preserving newlines is important because it allows the generated code to
310 initializer = ' = ${_getOriginalCode(code, field.initializer)}'; 325 // be debugged without needing a source map.)
311 } 326 //
327 // For example:
328 //
329 // @observable
330 // @otherMetaData
331 // Foo
332 // foo = 1, bar = 2,
333 // baz;
334 //
335 // Will be transformed into something like:
336 //
337 // @observable
338 // @OtherMetaData()
339 // Foo
340 // get foo => __foo; Foo __foo = 1; set foo ...; ... bar ...
341 // @observable @OtherMetaData() Foo get baz => __baz; Foo baz; ...
342 //
343 // Metadata is moved to the getter.
312 344
313 var name = field.name.name; 345 String metadata = '';
314 346 if (fields.variables.length > 1) {
315 // TODO(jmesserly): should we generate this one one line, so source maps 347 metadata = member.metadata
316 // don't break? 348 .map((m) => _getOriginalCode(code, m))
317 if (replace.length > 0) replace.write('\n\n$indent'); 349 .join(' ');
318 replace.write('''
319 $type __\$$name$initializer;
320 $type get $name => __\$$name;
321 set $name($type value) {
322 __\$$name = notifyPropertyChange(const Symbol('$name'), __\$$name, value);
323 }
324 '''.replaceAll('\n', '\n$indent'));
325 } 350 }
326 351
327 code.edit(begin, end, '$replace'); 352 for (int i = 0; i < fields.variables.length; i++) {
353 final field = fields.variables[i];
354 final name = field.name.name;
355
356 var beforeInit = 'get $name => __\$$name; $type __\$$name';
357
358 // The first field is expanded differently from subsequent fields, because
359 // we can reuse the metadata and type annotation.
360 if (i > 0) beforeInit = '$metadata $type $beforeInit';
361
362 code.edit(field.name.offset, field.name.end, beforeInit);
363
364 // Replace comma with semicolon
365 final end = _findFieldSeperator(field.endToken.next);
366 if (end.type == TokenType.COMMA) code.edit(end.offset, end.end, ';');
367
368 code.edit(end.end, end.end, ' set $name($type value) { '
369 '__\$$name = notifyPropertyChange(#$name, __\$$name, value); }');
370 }
328 } 371 }
372
373 Token _findFieldSeperator(Token token) {
374 while (token != null) {
375 if (token.type == TokenType.COMMA || token.type == TokenType.SEMICOLON) {
376 break;
377 }
378 token = token.next;
379 }
380 return token;
381 }
OLDNEW
« no previous file with comments | « no previous file | pkg/observe/test/transform_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698