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

Unified Diff: pkg/observe/lib/transform.dart

Issue 63173009: Ensure we run the observable transform on PolymerElement (fixes issue 14942) (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 1 month 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/observe/lib/transformer.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/observe/lib/transform.dart
diff --git a/pkg/observe/lib/transform.dart b/pkg/observe/lib/transform.dart
index 5e261c02b49b641fe30f36867373bd9a9ca3a9c5..4be227f026e8136123b319905a5c327244d3e7e8 100644
--- a/pkg/observe/lib/transform.dart
+++ b/pkg/observe/lib/transform.dart
@@ -3,413 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
/**
- * Code transform for @observable. The core transformation is relatively
- * straightforward, and essentially like an editor refactoring.
+ * Code transform for @observable. This library will be removed soon, it simply
+ * reexports `observe.transformer`.
*/
+// TODO(sigmund): deprecate and delete.
library observe.transform;
-import 'dart:async';
-
-import 'package:analyzer/src/generated/java_core.dart' show CharSequence;
-import 'package:analyzer/src/generated/ast.dart';
-import 'package:analyzer/src/generated/error.dart';
-import 'package:analyzer/src/generated/parser.dart';
-import 'package:analyzer/src/generated/scanner.dart';
-import 'package:barback/barback.dart';
-import 'package:source_maps/refactor.dart';
-import 'package:source_maps/span.dart' show SourceFile;
-
-/**
- * A [Transformer] that replaces observables based on dirty-checking with an
- * implementation based on change notifications.
- *
- * The transformation adds hooks for field setters and notifies the observation
- * system of the change.
- */
-class ObservableTransformer extends Transformer {
-
- Future<bool> isPrimary(Asset input) {
- if (input.id.extension != '.dart') return new Future.value(false);
- // Note: technically we should parse the file to find accurately the
- // observable annotation, but that seems expensive. It would require almost
- // as much work as applying the transform. We rather have some false
- // positives here, and then generate no outputs when we apply this
- // transform.
- return input.readAsString().then(
- (c) => c.contains("@observable") || c.contains("@published"));
- }
-
- Future apply(Transform transform) {
- return transform.primaryInput.readAsString().then((content) {
- var id = transform.primaryInput.id;
- // TODO(sigmund): improve how we compute this url
- var url = id.path.startsWith('lib/')
- ? 'package:${id.package}/${id.path.substring(4)}' : id.path;
- var sourceFile = new SourceFile.text(url, content);
- var transaction = _transformCompilationUnit(
- content, sourceFile, transform.logger);
- if (!transaction.hasEdits) {
- transform.addOutput(transform.primaryInput);
- return;
- }
- var printer = transaction.commit();
- // TODO(sigmund): emit source maps when barback supports it (see
- // dartbug.com/12340)
- printer.build(url);
- transform.addOutput(new Asset.fromString(id, printer.text));
- });
- }
-}
-
-TextEditTransaction _transformCompilationUnit(
- String inputCode, SourceFile sourceFile, TransformLogger logger) {
- var unit = _parseCompilationUnit(inputCode);
- var code = new TextEditTransaction(inputCode, sourceFile);
- for (var directive in unit.directives) {
- if (directive is LibraryDirective && _hasObservable(directive)) {
- logger.warning('@observable on a library no longer has any effect. '
- 'It should be placed on individual fields.',
- span: _getSpan(sourceFile, directive));
- break;
- }
- }
-
- for (var declaration in unit.declarations) {
- if (declaration is ClassDeclaration) {
- _transformClass(declaration, code, sourceFile, logger);
- } else if (declaration is TopLevelVariableDeclaration) {
- if (_hasObservable(declaration)) {
- logger.warning('Top-level fields can no longer be observable. '
- 'Observable fields should be put in an observable objects.',
- span: _getSpan(sourceFile, declaration));
- }
- }
- }
- return code;
-}
-
-/** Parse [code] using analyzer. */
-CompilationUnit _parseCompilationUnit(String code) {
- var errorListener = new _ErrorCollector();
- var reader = new CharSequenceReader(new CharSequence(code));
- var scanner = new Scanner(null, reader, errorListener);
- var token = scanner.tokenize();
- var parser = new Parser(null, errorListener);
- return parser.parseCompilationUnit(token);
-}
-
-class _ErrorCollector extends AnalysisErrorListener {
- final errors = <AnalysisError>[];
- onError(error) => errors.add(error);
-}
-
-_getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end);
-
-/** True if the node has the `@observable` or `@published` annotation. */
-// TODO(jmesserly): it is not good to be hard coding Polymer support here.
-bool _hasObservable(AnnotatedNode node) =>
- node.metadata.any(_isObservableAnnotation);
-
-// TODO(jmesserly): this isn't correct if the annotation has been imported
-// with a prefix, or cases like that. We should technically be resolving, but
-// that is expensive in analyzer, so it isn't feasible yet.
-bool _isObservableAnnotation(Annotation node) =>
- _isAnnotationContant(node, 'observable') ||
- _isAnnotationContant(node, 'published') ||
- _isAnnotationType(node, 'ObservableProperty') ||
- _isAnnotationType(node, 'PublishedProperty');
-
-bool _isAnnotationContant(Annotation m, String name) =>
- m.name.name == name && m.constructorName == null && m.arguments == null;
-
-bool _isAnnotationType(Annotation m, String name) => m.name == name;
-
-void _transformClass(ClassDeclaration cls, TextEditTransaction code,
- SourceFile file, TransformLogger logger) {
-
- if (_hasObservable(cls)) {
- logger.warning('@observable on a class no longer has any effect. '
- 'It should be placed on individual fields.',
- span: _getSpan(file, cls));
- }
-
- // We'd like to track whether observable was declared explicitly, otherwise
- // report a warning later below. Because we don't have type analysis (only
- // syntactic understanding of the code), we only report warnings that are
- // known to be true.
- var explicitObservable = false;
- var implicitObservable = false;
- if (cls.extendsClause != null) {
- var id = _getSimpleIdentifier(cls.extendsClause.superclass.name);
- if (id.name == 'Observable') {
- code.edit(id.offset, id.end, 'ChangeNotifier');
- explicitObservable = true;
- } else if (id.name == 'ChangeNotifier') {
- explicitObservable = true;
- } else if (id.name != 'HtmlElement' && id.name != 'CustomElement'
- && id.name != 'Object') {
- // TODO(sigmund): this is conservative, consider using type-resolution to
- // improve this check.
- implicitObservable = true;
- }
- }
-
- if (cls.withClause != null) {
- for (var type in cls.withClause.mixinTypes) {
- var id = _getSimpleIdentifier(type.name);
- if (id.name == 'Observable') {
- code.edit(id.offset, id.end, 'ChangeNotifier');
- explicitObservable = true;
- break;
- } else if (id.name == 'ChangeNotifier') {
- explicitObservable = true;
- break;
- } else {
- // TODO(sigmund): this is conservative, consider using type-resolution
- // to improve this check.
- implicitObservable = true;
- }
- }
- }
-
- if (cls.implementsClause != null) {
- // TODO(sigmund): consider adding type-resolution to give a more precise
- // answer.
- implicitObservable = true;
- }
-
- var declaresObservable = explicitObservable || implicitObservable;
-
- // Track fields that were transformed.
- var instanceFields = new Set<String>();
- var getters = new List<String>();
- var setters = new List<String>();
-
- for (var member in cls.members) {
- if (member is FieldDeclaration) {
- if (member.isStatic) {
- if (_hasObservable(member)){
- logger.warning('Static fields can no longer be observable. '
- 'Observable fields should be put in an observable objects.',
- span: _getSpan(file, member));
- }
- continue;
- }
- if (_hasObservable(member)) {
- if (!declaresObservable) {
- logger.warning('Observable fields should be put in an observable '
- 'objects. Please declare that this class extends from '
- 'Observable, includes Observable, or implements '
- 'Observable.',
- span: _getSpan(file, member));
- }
- _transformFields(file, member, code, logger);
-
- var names = member.fields.variables.map((v) => v.name.name);
-
- getters.addAll(names);
- if (!_isReadOnly(member.fields)) {
- setters.addAll(names);
- instanceFields.addAll(names);
- }
- }
- }
- // TODO(jmesserly): this is a temporary workaround until we can remove
- // getValueWorkaround and setValueWorkaround.
- if (member is MethodDeclaration) {
- if (_hasKeyword(member.propertyKeyword, Keyword.GET)) {
- getters.add(member.name.name);
- } else if (_hasKeyword(member.propertyKeyword, Keyword.SET)) {
- setters.add(member.name.name);
- }
- }
- }
-
- // If nothing was @observable, bail.
- if (instanceFields.length == 0) return;
-
- if (!explicitObservable) _mixinObservable(cls, code);
-
- // Fix initializers, because they aren't allowed to call the setter.
- for (var member in cls.members) {
- if (member is ConstructorDeclaration) {
- _fixConstructor(member, code, instanceFields);
- }
- }
-}
-
-/** Adds "with ChangeNotifier" and associated implementation. */
-void _mixinObservable(ClassDeclaration cls, TextEditTransaction code) {
- // Note: we need to be careful to put the with clause after extends, but
- // before implements clause.
- if (cls.withClause != null) {
- var pos = cls.withClause.end;
- code.edit(pos, pos, ', ChangeNotifier');
- } else if (cls.extendsClause != null) {
- var pos = cls.extendsClause.end;
- code.edit(pos, pos, ' with ChangeNotifier ');
- } else {
- var params = cls.typeParameters;
- var pos = params != null ? params.end : cls.name.end;
- code.edit(pos, pos, ' extends ChangeNotifier ');
- }
-}
-
-SimpleIdentifier _getSimpleIdentifier(Identifier id) =>
- id is PrefixedIdentifier ? id.identifier : id;
-
-
-bool _hasKeyword(Token token, Keyword keyword) =>
- token is KeywordToken && token.keyword == keyword;
-
-String _getOriginalCode(TextEditTransaction code, ASTNode node) =>
- code.original.substring(node.offset, node.end);
-
-void _fixConstructor(ConstructorDeclaration ctor, TextEditTransaction code,
- Set<String> changedFields) {
-
- // Fix normal initializers
- for (var initializer in ctor.initializers) {
- if (initializer is ConstructorFieldInitializer) {
- var field = initializer.fieldName;
- if (changedFields.contains(field.name)) {
- code.edit(field.offset, field.end, '__\$${field.name}');
- }
- }
- }
-
- // Fix "this." initializer in parameter list. These are tricky:
- // we need to preserve the name and add an initializer.
- // Preserving the name is important for named args, and for dartdoc.
- // BEFORE: Foo(this.bar, this.baz) { ... }
- // AFTER: Foo(bar, baz) : __$bar = bar, __$baz = baz { ... }
-
- var thisInit = [];
- for (var param in ctor.parameters.parameters) {
- if (param is DefaultFormalParameter) {
- param = param.parameter;
- }
- if (param is FieldFormalParameter) {
- var name = param.identifier.name;
- if (changedFields.contains(name)) {
- thisInit.add(name);
- // Remove "this." but keep everything else.
- code.edit(param.thisToken.offset, param.period.end, '');
- }
- }
- }
-
- if (thisInit.length == 0) return;
-
- // TODO(jmesserly): smarter formatting with indent, etc.
- var inserted = thisInit.map((i) => '__\$$i = $i').join(', ');
-
- int offset;
- if (ctor.separator != null) {
- offset = ctor.separator.end;
- inserted = ' $inserted,';
- } else {
- offset = ctor.parameters.end;
- inserted = ' : $inserted';
- }
-
- code.edit(offset, offset, inserted);
-}
-
-bool _isReadOnly(VariableDeclarationList fields) {
- return _hasKeyword(fields.keyword, Keyword.CONST) ||
- _hasKeyword(fields.keyword, Keyword.FINAL);
-}
-
-void _transformFields(SourceFile file, FieldDeclaration member,
- TextEditTransaction code, TransformLogger logger) {
-
- final fields = member.fields;
- if (_isReadOnly(fields)) return;
-
- // Private fields aren't supported:
- for (var field in fields.variables) {
- final name = field.name.name;
- if (Identifier.isPrivateName(name)) {
- logger.warning('Cannot make private field $name observable.',
- span: _getSpan(file, field));
- return;
- }
- }
-
- // Unfortunately "var" doesn't work in all positions where type annotations
- // are allowed, such as "var get name". So we use "dynamic" instead.
- var type = 'dynamic';
- if (fields.type != null) {
- type = _getOriginalCode(code, fields.type);
- } else if (_hasKeyword(fields.keyword, Keyword.VAR)) {
- // Replace 'var' with 'dynamic'
- code.edit(fields.keyword.offset, fields.keyword.end, type);
- }
-
- // Note: the replacements here are a bit subtle. It needs to support multiple
- // fields declared via the same @observable, as well as preserving newlines.
- // (Preserving newlines is important because it allows the generated code to
- // be debugged without needing a source map.)
- //
- // For example:
- //
- // @observable
- // @otherMetaData
- // Foo
- // foo = 1, bar = 2,
- // baz;
- //
- // Will be transformed into something like:
- //
- // @reflectable @observable
- // @OtherMetaData()
- // Foo
- // get foo => __foo; Foo __foo = 1; @reflectable set foo ...; ...
- // @observable @OtherMetaData() Foo get baz => __baz; Foo baz; ...
- //
- // Metadata is moved to the getter.
-
- String metadata = '';
- if (fields.variables.length > 1) {
- metadata = member.metadata
- .map((m) => _getOriginalCode(code, m))
- .join(' ');
- metadata = '@reflectable $metadata';
- }
-
- for (int i = 0; i < fields.variables.length; i++) {
- final field = fields.variables[i];
- final name = field.name.name;
-
- var beforeInit = 'get $name => __\$$name; $type __\$$name';
-
- // The first field is expanded differently from subsequent fields, because
- // we can reuse the metadata and type annotation.
- if (i == 0) {
- final begin = member.metadata.first.offset;
- code.edit(begin, begin, '@reflectable ');
- } else {
- beforeInit = '$metadata $type $beforeInit';
- }
-
- code.edit(field.name.offset, field.name.end, beforeInit);
-
- // Replace comma with semicolon
- final end = _findFieldSeperator(field.endToken.next);
- if (end.type == TokenType.COMMA) code.edit(end.offset, end.end, ';');
-
- code.edit(end.end, end.end, ' @reflectable set $name($type value) { '
- '__\$$name = notifyPropertyChange(#$name, __\$$name, value); }');
- }
-}
-
-Token _findFieldSeperator(Token token) {
- while (token != null) {
- if (token.type == TokenType.COMMA || token.type == TokenType.SEMICOLON) {
- break;
- }
- token = token.next;
- }
- return token;
-}
+export 'transformer.dart';
« no previous file with comments | « no previous file | pkg/observe/lib/transformer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698