Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 97 } | 97 } |
| 98 | 98 |
| 99 class _ErrorCollector extends AnalysisErrorListener { | 99 class _ErrorCollector extends AnalysisErrorListener { |
| 100 final errors = <AnalysisError>[]; | 100 final errors = <AnalysisError>[]; |
| 101 onError(error) => errors.add(error); | 101 onError(error) => errors.add(error); |
| 102 } | 102 } |
| 103 | 103 |
| 104 _getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end); | 104 _getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end); |
| 105 | 105 |
| 106 /** True if the node has the `@observable` or `@published` annotation. */ | 106 /** True if the node has the `@observable` or `@published` annotation. */ |
| 107 // TODO(jmesserly): it is not good to be hard coding these. We should do a | 107 // TODO(jmesserly): it is not good to be hard coding Polymer support here. |
| 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) => | 108 bool _hasObservable(AnnotatedNode node) => |
| 111 _hasAnnotation(node, 'observable') || _hasAnnotation(node, 'published'); | 109 node.metadata.any(_isObservableAnnotation); |
| 112 | 110 |
| 113 bool _hasAnnotation(AnnotatedNode node, String name) { | 111 // 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 | 112 // 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 | 113 // that is expensive in analyzer_experimental, so it isn't feasible yet. |
| 116 // that is expensive. | 114 bool _isObservableAnnotation(Annotation node) => |
| 117 return node.metadata.any((m) => m.name.name == name && | 115 _isAnnotationContant(node, 'observable') || |
| 118 m.constructorName == null && m.arguments == null); | 116 _isAnnotationContant(node, 'published') || |
| 119 } | 117 _isAnnotationType(node, 'ObservableProperty') || |
| 118 _isAnnotationType(node, 'PublishedProperty'); | |
| 119 | |
| 120 bool _isAnnotationContant(Annotation m, String name) => | |
| 121 m.name.name == name && m.constructorName == null && m.arguments == null; | |
| 122 | |
| 123 bool _isAnnotationType(Annotation m, String name) => m.name == name; | |
| 120 | 124 |
| 121 void _transformClass(ClassDeclaration cls, TextEditTransaction code, | 125 void _transformClass(ClassDeclaration cls, TextEditTransaction code, |
| 122 SourceFile file, TransformLogger logger) { | 126 SourceFile file, TransformLogger logger) { |
| 123 | 127 |
| 124 if (_hasObservable(cls)) { | 128 if (_hasObservable(cls)) { |
| 125 logger.warning('@observable on a class no longer has any effect. ' | 129 logger.warning('@observable on a class no longer has any effect. ' |
| 126 'It should be placed on individual fields.', | 130 'It should be placed on individual fields.', |
| 127 span: _getSpan(file, cls)); | 131 span: _getSpan(file, cls)); |
| 128 } | 132 } |
| 129 | 133 |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 330 // @otherMetaData | 334 // @otherMetaData |
| 331 // Foo | 335 // Foo |
| 332 // foo = 1, bar = 2, | 336 // foo = 1, bar = 2, |
| 333 // baz; | 337 // baz; |
| 334 // | 338 // |
| 335 // Will be transformed into something like: | 339 // Will be transformed into something like: |
| 336 // | 340 // |
| 337 // @observable | 341 // @observable |
| 338 // @OtherMetaData() | 342 // @OtherMetaData() |
| 339 // Foo | 343 // Foo |
| 340 // get foo => __foo; Foo __foo = 1; set foo ...; ... bar ... | 344 // get foo => __foo; Foo __foo = 1; @reflectable set foo ...; ... |
| 341 // @observable @OtherMetaData() Foo get baz => __baz; Foo baz; ... | 345 // @observable @OtherMetaData() Foo get baz => __baz; Foo baz; ... |
|
Siggi Cherem (dart-lang)
2013/10/15 21:52:18
should we include @reflectable on the 'get' part o
Jennifer Messerly
2013/10/15 22:03:29
good catch.
| |
| 342 // | 346 // |
| 343 // Metadata is moved to the getter. | 347 // Metadata is moved to the getter. |
| 344 | 348 |
| 345 String metadata = ''; | 349 String metadata = ''; |
| 346 if (fields.variables.length > 1) { | 350 if (fields.variables.length > 1) { |
| 347 metadata = member.metadata | 351 metadata = member.metadata |
| 348 .map((m) => _getOriginalCode(code, m)) | 352 .map((m) => _getOriginalCode(code, m)) |
| 349 .join(' '); | 353 .join(' '); |
| 354 metadata = '@reflectable $metadata'; | |
| 350 } | 355 } |
| 351 | 356 |
| 352 for (int i = 0; i < fields.variables.length; i++) { | 357 for (int i = 0; i < fields.variables.length; i++) { |
| 353 final field = fields.variables[i]; | 358 final field = fields.variables[i]; |
| 354 final name = field.name.name; | 359 final name = field.name.name; |
| 355 | 360 |
| 356 var beforeInit = 'get $name => __\$$name; $type __\$$name'; | 361 var beforeInit = 'get $name => __\$$name; $type __\$$name'; |
| 357 | 362 |
| 358 // The first field is expanded differently from subsequent fields, because | 363 // The first field is expanded differently from subsequent fields, because |
| 359 // we can reuse the metadata and type annotation. | 364 // we can reuse the metadata and type annotation. |
| 360 if (i > 0) beforeInit = '$metadata $type $beforeInit'; | 365 if (i == 0) { |
| 366 final begin = member.metadata.first.offset; | |
| 367 code.edit(begin, begin, '@reflectable '); | |
| 368 } else { | |
| 369 beforeInit = '$metadata $type $beforeInit'; | |
| 370 } | |
| 361 | 371 |
| 362 code.edit(field.name.offset, field.name.end, beforeInit); | 372 code.edit(field.name.offset, field.name.end, beforeInit); |
| 363 | 373 |
| 364 // Replace comma with semicolon | 374 // Replace comma with semicolon |
| 365 final end = _findFieldSeperator(field.endToken.next); | 375 final end = _findFieldSeperator(field.endToken.next); |
| 366 if (end.type == TokenType.COMMA) code.edit(end.offset, end.end, ';'); | 376 if (end.type == TokenType.COMMA) code.edit(end.offset, end.end, ';'); |
| 367 | 377 |
| 368 code.edit(end.end, end.end, ' set $name($type value) { ' | 378 code.edit(end.end, end.end, ' @reflectable set $name($type value) { ' |
| 369 '__\$$name = notifyPropertyChange(#$name, __\$$name, value); }'); | 379 '__\$$name = notifyPropertyChange(#$name, __\$$name, value); }'); |
| 370 } | 380 } |
| 371 } | 381 } |
| 372 | 382 |
| 373 Token _findFieldSeperator(Token token) { | 383 Token _findFieldSeperator(Token token) { |
| 374 while (token != null) { | 384 while (token != null) { |
| 375 if (token.type == TokenType.COMMA || token.type == TokenType.SEMICOLON) { | 385 if (token.type == TokenType.COMMA || token.type == TokenType.SEMICOLON) { |
| 376 break; | 386 break; |
| 377 } | 387 } |
| 378 token = token.next; | 388 token = token.next; |
| 379 } | 389 } |
| 380 return token; | 390 return token; |
| 381 } | 391 } |
| OLD | NEW |