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

Unified Diff: lib/src/observable_transform.dart

Issue 12225039: Support for observable models, fixes #259 (Closed) Base URL: https://github.com/dart-lang/web-ui.git@master
Patch Set: Created 7 years, 10 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/src/info.dart ('k') | lib/src/refactor.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/src/observable_transform.dart
diff --git a/lib/src/observable_transform.dart b/lib/src/observable_transform.dart
new file mode 100644
index 0000000000000000000000000000000000000000..53f1c0dbeb1361b0f110480b9902f316464100aa
--- /dev/null
+++ b/lib/src/observable_transform.dart
@@ -0,0 +1,211 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// 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. You can find the
+ * core implementation in [transformClass], which is ultimately called by
+ * [transformObservables], the entry point to this library.
+ */
+library observable_transform;
+
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:analyzer_experimental/src/generated/error.dart';
+import 'package:analyzer_experimental/src/generated/scanner.dart';
+import 'dart_parser.dart';
+import 'messages.dart';
+import 'refactor.dart';
+
+/**
+ * Transform types in Dart [userCode] marked with `@observable` by hooking all
+ * field setters, and notifying the observation system of the change. If the
+ * code was changed this returns true, otherwise returns false. Modified code
+ * can be found in [userCode.code].
+ *
+ * Note: there is no special checking for transitive immutability. It is up to
+ * the rest of the observation system to handle check for this condition and
+ * handle it appropriately. We do not want to violate reference equality of
+ * any fields that are set into the object.
+ */
+bool transformObservables(DartCodeInfo userCode, Messages messages) {
+ if (userCode == null || userCode.compilationUnit == null) return false;
+
+ var oldCode = userCode.code;
+ var transaction = new TextEditTransaction(oldCode);
+ transformCompilationUnit(userCode.compilationUnit, transaction);
+
+ var newCode = transaction.commit();
+ if (identical(oldCode, newCode)) return false;
+
+ // Replace the code
+ userCode.code = newCode;
+ return true;
+}
+
+void transformCompilationUnit(CompilationUnit unit, TextEditTransaction code) {
+ bool observeAll = unit.directives.any(
+ (d) => d is LibraryDirective && hasObservable(d));
+
+ for (var declaration in unit.declarations) {
+ if (declaration is ClassDeclaration) {
+ transformClass(declaration, code, observeAll);
+ } else if (declaration is TopLevelVariableDeclaration) {
+ if (observeAll || hasObservable(declaration)) {
+ transformTopLevelField(declaration, code);
+ }
+ }
+ }
+}
+
+/** True if the code has the `@observable` annotation. */
+bool hasObservable(AnnotatedNode node) {
+ // TODO(jmesserly): this isn't correct if observable has been imported
+ // with a prefix, or cases like that. We should technically be resolving, but
+ // that is expensive.
+ return node.metadata.any((m) => m.name.name == 'observable' &&
+ m.constructorName == null && m.arguments == null);
+}
+
+void transformClass(ClassDeclaration cls, TextEditTransaction code,
+ bool observeAll) {
+
+ observeAll = observeAll || hasObservable(cls);
+
+ var changedFields = new Set<String>();
+ for (var member in cls.members) {
+ if (member is FieldDeclaration) {
+ if (observeAll || hasObservable(member)) {
+ transformClassFields(member, code, changedFields);
+ }
+ }
+ }
+
+ if (changedFields.length == 0) return;
+
+ // Fix initializers, because they aren't allowed to call the setter.
+ for (var member in cls.members) {
+ if (member is ConstructorDeclaration) {
+ fixConstructor(member, code, changedFields);
+ }
+ }
+}
+
+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 transformTopLevelField(TopLevelVariableDeclaration field,
+ TextEditTransaction code) {
+ transformFields(field.variables, code, field.offset, field.end);
+}
+
+void transformClassFields(FieldDeclaration member, TextEditTransaction code,
+ Set<String> changedFields) {
+
+ transformFields(member.fields, code, member.offset, member.end,
+ isStatic: hasKeyword(member.keyword, Keyword.STATIC),
+ changedFields: changedFields);
+}
+
+
+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 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);
+}
+
+void transformFields(VariableDeclarationList fields, TextEditTransaction code,
+ int begin, int end, {bool isStatic: false, Set<String> changedFields}) {
+
+ if (hasKeyword(fields.keyword, Keyword.CONST) ||
+ hasKeyword(fields.keyword, Keyword.FINAL)) {
+ return;
+ }
+
+ var indent = guessIndent(code.original, begin);
+ var replace = new StringBuffer();
+
+ // 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);
+ }
+
+ var mod = isStatic ? 'static ' : '';
+
+ for (var field in fields.variables) {
+ var initializer = '';
+ if (field.initializer != null) {
+ initializer = ' = ${getOriginalCode(code, field.initializer)}';
+ }
+
+ var name = field.name.name;
+
+ if (replace.length > 0) replace.add('\n\n$indent');
+ replace.add('''
+${mod}$type __\$$name$initializer;
+${mod}Object __obs\$$name;
+${mod}$type get $name {
+ if (autogenerated.observeReads) {
+ __obs\$$name = autogenerated.notifyRead(__obs\$$name);
+ }
+ return __\$$name;
+}
+${mod}set $name($type value) {
+ if (__obs\$$name != null && __\$$name != value) {
+ __obs\$$name = autogenerated.notifyWrite(__obs\$$name);
+ }
+ __\$$name = value;
+}'''.replaceAll('\n', '\n$indent'));
+
+ if (changedFields != null) changedFields.add(name);
+ }
+
+ code.edit(begin, end, '$replace');
+}
« no previous file with comments | « lib/src/info.dart ('k') | lib/src/refactor.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698