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 |