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

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
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) {
33 return false;
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 would this fit in the prev line?
Jennifer Messerly 2013/02/14 00:38:09 Done.
34 }
35 var oldCode = userCode.code;
36 var transaction = new TextEditTransaction(oldCode);
37 transformCompilationUnit(userCode.compilationUnit, transaction);
38
39 var newCode = transaction.commit();
40 if (identical(oldCode, newCode)) {
41 return false;
42 } else {
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 nit: remove else... if (identical(...)) return fa
Jennifer Messerly 2013/02/14 00:38:09 Done.
43 // Replace the code
44 userCode.code = newCode;
45 return true;
46 }
47 }
48
49 void transformCompilationUnit(CompilationUnit unit, TextEditTransaction code) {
50 bool observeAll = unit.directives.any(
51 (d) => d is LibraryDirective && hasObservable(d));
52
53 for (var declaration in unit.declarations) {
54 if (declaration is ClassDeclaration) {
55 transformClass(declaration, code, observeAll);
56 } else if (declaration is TopLevelVariableDeclaration) {
57 if (observeAll || hasObservable(declaration)) {
58 transformTopLevelField(declaration, code);
59 }
60 }
61 }
62 }
63
64 /** True if the code has the `@observable` annotation. */
65 bool hasObservable(AnnotatedNode node) {
66 // TODO(jmesserly): this isn't correct if observable has been imported
67 // with a prefix, or cases like that. We should technically be resolving, but
68 // that is expensive.
69 return node.metadata.any((m) => m.name.name == 'observable' &&
70 m.constructorName == null && m.arguments == null);
71 }
72
73 void transformClass(ClassDeclaration cls, TextEditTransaction code,
74 bool observeAll) {
75
76 observeAll = observeAll || hasObservable(cls);
77
78 var changedFields = new Set<String>();
79 for (var member in cls.members) {
80 if (member is FieldDeclaration) {
81 if (observeAll || hasObservable(member)) {
82 transformClassFields(member, code, changedFields);
83 }
84 }
85 }
86
87 if (changedFields.length == 0) return;
88
89 // Fix initializers, because they aren't allowed to call the setter.
90 for (var member in cls.members) {
91 if (member is ConstructorDeclaration) {
92 fixConstructor(member, code, changedFields);
93 }
94 }
95 }
96
97 bool hasKeyword(Token token, Keyword keyword) =>
98 token is KeywordToken && token.keyword == keyword;
99
100 String getOriginalCode(TextEditTransaction code, ASTNode node) =>
101 code.original.substring(node.offset, node.end);
102
103 void transformTopLevelField(TopLevelVariableDeclaration field,
104 TextEditTransaction code) {
105 transformFields(field.variables, code, field.offset, field.end);
106 }
107
108 void transformClassFields(FieldDeclaration member, TextEditTransaction code,
109 Set<String> changedFields) {
110
111 transformFields(member.fields, code, member.offset, member.end,
112 isStatic: hasKeyword(member.keyword, Keyword.STATIC),
113 changedFields: changedFields);
114 }
115
116
117 void fixConstructor(ConstructorDeclaration ctor, TextEditTransaction code,
118 Set<String> changedFields) {
119
120 // Fix normal initializers
121 for (var initializer in ctor.initializers) {
122 if (initializer is ConstructorFieldInitializer) {
123 var field = initializer.fieldName;
124 if (changedFields.contains(field.name)) {
125 code.edit(field.offset, field.end, '__\$${field.name}');
126 }
127 }
128 }
129
130 // Fix "this." initializer in parameter list. These are tricky:
131 // we need to preserve the name and add an initializer.
132 // BEFORE: Foo(this.bar, this.baz) { ... }
133 // AFTER: Foo(bar, baz) : __$bar = bar, __$baz = baz { ... }
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 why can't AFTER be: Foo(this.__$bar, this.__$baz)
Jennifer Messerly 2013/02/13 20:21:20 It's mainly to handle named arguments. However par
134
135 var thisInit = [];
136 for (var param in ctor.parameters.parameters) {
137 if (param is FieldFormalParameter) {
138 var name = param.identifier.name;
139 if (changedFields.contains(name)) {
140 thisInit.add(name);
141 // Remove "this." but keep everything else.
142 code.edit(param.thisToken.offset, param.period.end, '');
143 }
144 }
145 }
146
147 if (thisInit.length == 0) return;
148
149 // TODO(jmesserly): smarter formatting with indent, etc.
150 var inserted = thisInit.map((i) => '__\$$i = $i').join(', ');
151
152 int offset;
153 if (ctor.separator != null) {
154 offset = ctor.separator.end;
155 inserted = ' $inserted,';
156 } else {
157 offset = ctor.parameters.end;
158 inserted = ' : $inserted';
159 }
160
161 code.edit(offset, offset, inserted);
162 }
163
164 void transformFields(VariableDeclarationList fields, TextEditTransaction code,
165 int begin, int end, {bool isStatic: false, Set<String> changedFields}) {
166
167 if (hasKeyword(fields.keyword, Keyword.CONST) ||
168 hasKeyword(fields.keyword, Keyword.FINAL)) {
169 return;
170 }
171
172 var indent = guessIndent(code.original, begin);
173 var replace = new StringBuffer();
174
175 var type = 'dynamic';
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 do you prefer 'dynamic' over 'var'?
Jennifer Messerly 2013/02/13 20:21:20 I had to change it because "var get fieldName" doe
Siggi Cherem (dart-lang) 2013/02/13 20:41:13 makes sense, thanks
Jennifer Messerly 2013/02/14 00:38:09 Done.
176 if (fields.type != null) {
177 type = getOriginalCode(code, fields.type);
178 }
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 totally optional nit, consider using ternary expre
Jennifer Messerly 2013/02/13 20:21:20 Here's my thinking: I like ternary expressions. Bu
Siggi Cherem (dart-lang) 2013/02/13 20:41:13 np - I figured it had something to do with long li
Jennifer Messerly 2013/02/14 00:38:09 gotcha :)
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

Powered by Google App Engine
This is Rietveld 408576698