OLD | NEW |
(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:compiler_unsupported/implementation/elements/elements.dart'; |
| 14 import 'package:compiler_unsupported/implementation/scanner/scannerlib.dart'; |
| 15 import 'package:compiler_unsupported/implementation/tree/tree.dart'; |
| 16 import 'package:compiler_unsupported/implementation/util/util.dart'; |
| 17 import 'dart_parser.dart'; |
| 18 import 'info.dart'; |
| 19 import 'messages.dart'; |
| 20 import 'refactor.dart'; |
| 21 |
| 22 // TODO(jmesserly): this doesn't work for "this." in constructors. |
| 23 /** |
| 24 * Transform types in Dart [code] marked with `@observable` by hooking all field |
| 25 * setters, and notifying the observation system of the change. If the code was |
| 26 * changed this returns true, otherwise returns false. Modified code can be |
| 27 * found in [info.userCode.code]. |
| 28 * |
| 29 * Note: there is no special checking for transitive immutability. It is up to |
| 30 * the rest of the observation system to handle check for this condition and. |
| 31 * handle it appropriately. We do not want to violate reference equality of |
| 32 * any fields that are set into the object. |
| 33 */ |
| 34 bool transformObservables(LibraryInfo info, {Messages messages}) { |
| 35 if (info.userCode == null) return false; |
| 36 var oldCode = info.userCode.code; |
| 37 |
| 38 // Avoid parsing unless we think it has @observable. |
| 39 // TODO(jmesserly): investigate why the dart2js parser isn't fast enough. |
| 40 // If we don't do this check, it adds ~100ms overhead, even for the simple |
| 41 // TodoMVC example. Because that example uses @observable, it's not just VM |
| 42 // warm up time for the dart2js code, so I'm not sure what's going on. |
| 43 if (!oldCode.contains('@observable')) return false; |
| 44 |
| 45 var parsed = new DartCodeParser.parse(info.inputPath, oldCode, |
| 46 messages: messages); |
| 47 |
| 48 var newCode = _transformParsedCode(parsed); |
| 49 if (identical(oldCode, newCode)) return false; |
| 50 |
| 51 // Replace the code |
| 52 info.userCode.code = newCode; |
| 53 return true; |
| 54 } |
| 55 |
| 56 String _transformParsedCode(DartCodeParser parser) { |
| 57 // If parsing failed, don't try to transform the code. |
| 58 if (!parser.success) return parser.code; |
| 59 |
| 60 var transformedCode = new TextEditTransaction(parser.code); |
| 61 for (var element in parser.unit.localMembers) { |
| 62 if (element is PartialClassElement) { |
| 63 transformClass(element, parser, transformedCode); |
| 64 } |
| 65 } |
| 66 return transformedCode.commit(); |
| 67 } |
| 68 |
| 69 void transformClass(PartialClassElement element, DartCodeParser parser, |
| 70 TextEditTransaction code) { |
| 71 |
| 72 var classNode = parser.parseClass(element); |
| 73 |
| 74 // TODO(jmesserly): this isn't correct if observable has been imported |
| 75 // with a prefix, or cases like that. We should technically be resolving, but |
| 76 // that is expensive. |
| 77 bool hasObservable = false; |
| 78 for (var metadata in element.metadata) { |
| 79 var node = metadata.parseNode(parser.diagnostics); |
| 80 if (node is Send && node.selector is Identifier && |
| 81 node.selector.source.slowToString() == 'observable') { |
| 82 hasObservable = true; |
| 83 } |
| 84 } |
| 85 |
| 86 if (!hasObservable) return; |
| 87 |
| 88 bool transformed = false; |
| 89 for (var member in element.localMembers) { |
| 90 if (member.isField()) { |
| 91 VariableElement field = member; |
| 92 if (transformFields(field.variables, parser.diagnostics, code)) { |
| 93 // TODO(jmesserly): if class was transformed, mixin Observable |
| 94 transformed = true; |
| 95 } |
| 96 } |
| 97 } |
| 98 |
| 99 } |
| 100 |
| 101 bool transformFields(VariableListElement member, DiagnosticListener diagnostics, |
| 102 TextEditTransaction code) { |
| 103 |
| 104 if (member.cachedNode != null) { |
| 105 // If the cached node already exists, it means we're seeing something like |
| 106 // "bar" in this example code, and we've already generated it: |
| 107 // var foo, bar; |
| 108 // We need to transform both fields together. |
| 109 return false; |
| 110 } |
| 111 |
| 112 var mod = member.modifiers; |
| 113 if (mod.isStatic() || mod.isAbstract() || mod.isFinal() || mod.isConst()) { |
| 114 return false; |
| 115 } |
| 116 |
| 117 var defs = member.parseNode(diagnostics); |
| 118 int begin = defs.getBeginToken().charOffset; |
| 119 int end = defs.getEndToken().charOffset + 1; |
| 120 var indent = guessIndent(code.original, begin); |
| 121 var replace = new StringBuffer(); |
| 122 |
| 123 for (var def in defs.definitions) { |
| 124 Identifier identifier; |
| 125 String initializer; |
| 126 if (def is Identifier) { |
| 127 identifier = def; |
| 128 initializer = ''; |
| 129 } else { |
| 130 SendSet send = def; |
| 131 identifier = send.selector; |
| 132 initializer = ' = ${send.arguments.head}'; |
| 133 } |
| 134 |
| 135 var name = identifier.source.slowToString(); |
| 136 var type = defs.type; |
| 137 if (type == null && mod.isVar()) { |
| 138 type = 'var'; |
| 139 } |
| 140 |
| 141 if (replace.length > 0) replace.add('\n\n$indent'); |
| 142 replace.add(''' |
| 143 $type __\$$name$initializer; |
| 144 $type get $name { |
| 145 if (autogenerated.observeReads) { |
| 146 this.notifyRead(autogenerated.ChangeRecord.FIELD, '$name'); |
| 147 } |
| 148 return __\$$name; |
| 149 } |
| 150 set $name($type value) { |
| 151 if (this.hasObservers) { |
| 152 this.notifyChange(autogenerated.ChangeRecord.FIELD, '$name', __\$$name, valu
e); |
| 153 } |
| 154 __\$$name = value; |
| 155 }'''.replaceAll('\n', '\n$indent')); |
| 156 } |
| 157 |
| 158 code.edit(begin, end, '$replace'); |
| 159 return true; |
| 160 } |
OLD | NEW |