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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « lib/src/info.dart ('k') | lib/src/refactor.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
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.
4
5 /**
6 * Code transform for @observable. The core transformation is relatively
7 * straightforward, and essentially like an editor refactoring. You can find the
8 * core implementation in [transformClass], which is ultimately called by
9 * [transformObservables], the entry point to this library.
10 */
11 library observable_transform;
12
13 import 'package:analyzer_experimental/src/generated/ast.dart';
14 import 'package:analyzer_experimental/src/generated/error.dart';
15 import 'package:analyzer_experimental/src/generated/scanner.dart';
16 import 'dart_parser.dart';
17 import 'messages.dart';
18 import 'refactor.dart';
19
20 /**
21 * Transform types in Dart [userCode] marked with `@observable` by hooking all
22 * field setters, and notifying the observation system of the change. If the
23 * code was changed this returns true, otherwise returns false. Modified code
24 * can be found in [userCode.code].
25 *
26 * Note: there is no special checking for transitive immutability. It is up to
27 * the rest of the observation system to handle check for this condition and
28 * handle it appropriately. We do not want to violate reference equality of
29 * any fields that are set into the object.
30 */
31 bool transformObservables(DartCodeInfo userCode, Messages messages) {
32 if (userCode == null || userCode.compilationUnit == null) return false;
33
34 var oldCode = userCode.code;
35 var transaction = new TextEditTransaction(oldCode);
36 transformCompilationUnit(userCode.compilationUnit, transaction);
37
38 var newCode = transaction.commit();
39 if (identical(oldCode, newCode)) return false;
40
41 // Replace the code
42 userCode.code = newCode;
43 return true;
44 }
45
46 void transformCompilationUnit(CompilationUnit unit, TextEditTransaction code) {
47 bool observeAll = unit.directives.any(
48 (d) => d is LibraryDirective && hasObservable(d));
49
50 for (var declaration in unit.declarations) {
51 if (declaration is ClassDeclaration) {
52 transformClass(declaration, code, observeAll);
53 } else if (declaration is TopLevelVariableDeclaration) {
54 if (observeAll || hasObservable(declaration)) {
55 transformTopLevelField(declaration, code);
56 }
57 }
58 }
59 }
60
61 /** True if the code has the `@observable` annotation. */
62 bool hasObservable(AnnotatedNode node) {
63 // TODO(jmesserly): this isn't correct if observable has been imported
64 // with a prefix, or cases like that. We should technically be resolving, but
65 // that is expensive.
66 return node.metadata.any((m) => m.name.name == 'observable' &&
67 m.constructorName == null && m.arguments == null);
68 }
69
70 void transformClass(ClassDeclaration cls, TextEditTransaction code,
71 bool observeAll) {
72
73 observeAll = observeAll || hasObservable(cls);
74
75 var changedFields = new Set<String>();
76 for (var member in cls.members) {
77 if (member is FieldDeclaration) {
78 if (observeAll || hasObservable(member)) {
79 transformClassFields(member, code, changedFields);
80 }
81 }
82 }
83
84 if (changedFields.length == 0) return;
85
86 // Fix initializers, because they aren't allowed to call the setter.
87 for (var member in cls.members) {
88 if (member is ConstructorDeclaration) {
89 fixConstructor(member, code, changedFields);
90 }
91 }
92 }
93
94 bool hasKeyword(Token token, Keyword keyword) =>
95 token is KeywordToken && token.keyword == keyword;
96
97 String getOriginalCode(TextEditTransaction code, ASTNode node) =>
98 code.original.substring(node.offset, node.end);
99
100 void transformTopLevelField(TopLevelVariableDeclaration field,
101 TextEditTransaction code) {
102 transformFields(field.variables, code, field.offset, field.end);
103 }
104
105 void transformClassFields(FieldDeclaration member, TextEditTransaction code,
106 Set<String> changedFields) {
107
108 transformFields(member.fields, code, member.offset, member.end,
109 isStatic: hasKeyword(member.keyword, Keyword.STATIC),
110 changedFields: changedFields);
111 }
112
113
114 void fixConstructor(ConstructorDeclaration ctor, TextEditTransaction code,
115 Set<String> changedFields) {
116
117 // Fix normal initializers
118 for (var initializer in ctor.initializers) {
119 if (initializer is ConstructorFieldInitializer) {
120 var field = initializer.fieldName;
121 if (changedFields.contains(field.name)) {
122 code.edit(field.offset, field.end, '__\$${field.name}');
123 }
124 }
125 }
126
127 // Fix "this." initializer in parameter list. These are tricky:
128 // we need to preserve the name and add an initializer.
129 // Preserving the name is important for named args, and for dartdoc.
130 // BEFORE: Foo(this.bar, this.baz) { ... }
131 // AFTER: Foo(bar, baz) : __$bar = bar, __$baz = baz { ... }
132
133 var thisInit = [];
134 for (var param in ctor.parameters.parameters) {
135 if (param is FieldFormalParameter) {
136 var name = param.identifier.name;
137 if (changedFields.contains(name)) {
138 thisInit.add(name);
139 // Remove "this." but keep everything else.
140 code.edit(param.thisToken.offset, param.period.end, '');
141 }
142 }
143 }
144
145 if (thisInit.length == 0) return;
146
147 // TODO(jmesserly): smarter formatting with indent, etc.
148 var inserted = thisInit.map((i) => '__\$$i = $i').join(', ');
149
150 int offset;
151 if (ctor.separator != null) {
152 offset = ctor.separator.end;
153 inserted = ' $inserted,';
154 } else {
155 offset = ctor.parameters.end;
156 inserted = ' : $inserted';
157 }
158
159 code.edit(offset, offset, inserted);
160 }
161
162 void transformFields(VariableDeclarationList fields, TextEditTransaction code,
163 int begin, int end, {bool isStatic: false, Set<String> changedFields}) {
164
165 if (hasKeyword(fields.keyword, Keyword.CONST) ||
166 hasKeyword(fields.keyword, Keyword.FINAL)) {
167 return;
168 }
169
170 var indent = guessIndent(code.original, begin);
171 var replace = new StringBuffer();
172
173 // Unfortunately "var" doesn't work in all positions where type annotations
174 // are allowed, such as "var get name". So we use "dynamic" instead.
175 var type = 'dynamic';
176 if (fields.type != null) {
177 type = getOriginalCode(code, fields.type);
178 }
179
180 var mod = isStatic ? 'static ' : '';
181
182 for (var field in fields.variables) {
183 var initializer = '';
184 if (field.initializer != null) {
185 initializer = ' = ${getOriginalCode(code, field.initializer)}';
186 }
187
188 var name = field.name.name;
189
190 if (replace.length > 0) replace.add('\n\n$indent');
191 replace.add('''
192 ${mod}$type __\$$name$initializer;
193 ${mod}Object __obs\$$name;
194 ${mod}$type get $name {
195 if (autogenerated.observeReads) {
196 __obs\$$name = autogenerated.notifyRead(__obs\$$name);
197 }
198 return __\$$name;
199 }
200 ${mod}set $name($type value) {
201 if (__obs\$$name != null && __\$$name != value) {
202 __obs\$$name = autogenerated.notifyWrite(__obs\$$name);
203 }
204 __\$$name = value;
205 }'''.replaceAll('\n', '\n$indent'));
206
207 if (changedFields != null) changedFields.add(name);
208 }
209
210 code.edit(begin, end, '$replace');
211 }
OLDNEW
« 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