| OLD | NEW |
| 1 #!/usr/bin/env dart | 1 #!/usr/bin/env dart |
| 2 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 2 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 3 // for details. All rights reserved. Use of this source code is governed by a | 3 // for details. All rights reserved. Use of this source code is governed by a |
| 4 // BSD-style license that can be found in the LICENSE file. | 4 // BSD-style license that can be found in the LICENSE file. |
| 5 | 5 |
| 6 /// Command line tool to merge the SDK libraries and our patch files. | 6 /// Command line tool to merge the SDK libraries and our patch files. |
| 7 /// This is currently designed as an offline tool, but we could automate it. | 7 /// This is currently designed as an offline tool, but we could automate it. |
| 8 library ddc.tool.patch_sdk; | 8 library ddc.tool.patch_sdk; |
| 9 | 9 |
| 10 import 'dart:io'; | 10 import 'dart:io'; |
| 11 import 'dart:math' as math; | |
| 12 | 11 |
| 13 import 'package:analyzer/analyzer.dart'; | 12 import 'package:analyzer/analyzer.dart'; |
| 14 import 'package:path/path.dart' as path; | 13 import 'package:path/path.dart' as path; |
| 15 | 14 |
| 16 import 'input_sdk_src/lib/_internal/libraries.dart' as sdk; | 15 import 'input_sdk_src/lib/_internal/libraries.dart' as sdk; |
| 17 | 16 |
| 18 void main(List<String> argv) { | 17 void main(List<String> argv) { |
| 19 var toolDir = path.relative(path.dirname(Platform.script.path)); | 18 var toolDir = path.relative(path.dirname(Platform.script.path)); |
| 20 var sdkIn = path.join(toolDir, 'input_sdk_src', 'lib'); | 19 var sdkIn = path.join(toolDir, 'input_sdk_src', 'lib'); |
| 21 var patchIn = path.join(toolDir, 'input_sdk_patch'); | 20 var patchIn = path.join(toolDir, 'input_sdk_patch'); |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 93 /// Editorializing: the dart2js approach requires a Dart front end such as | 92 /// Editorializing: the dart2js approach requires a Dart front end such as |
| 94 /// package:analyzer to semantically model a feature beyond what is specified | 93 /// package:analyzer to semantically model a feature beyond what is specified |
| 95 /// in the Dart language. Since this feature is only for the convenience of | 94 /// in the Dart language. Since this feature is only for the convenience of |
| 96 /// writing the dart:* libraries, and not a tool given to Dart developers, it | 95 /// writing the dart:* libraries, and not a tool given to Dart developers, it |
| 97 /// seems like a non-ideal situation. Instead we keep the preprocessing simple. | 96 /// seems like a non-ideal situation. Instead we keep the preprocessing simple. |
| 98 List<String> _patchLibrary(List<String> partsContents, String patchContents) { | 97 List<String> _patchLibrary(List<String> partsContents, String patchContents) { |
| 99 var results = <StringEditBuffer>[]; | 98 var results = <StringEditBuffer>[]; |
| 100 | 99 |
| 101 // Parse the patch first. We'll need to extract bits of this as we go through | 100 // Parse the patch first. We'll need to extract bits of this as we go through |
| 102 // the other files. | 101 // the other files. |
| 103 var patchUnit = parseCompilationUnit(patchContents); | 102 var patchFinder = new PatchFinder.parseAndVisit(patchContents); |
| 104 var patchInfo = (new PatchFinder()..visitCompilationUnit(patchUnit)).patches; | |
| 105 | 103 |
| 106 // Merge `external` declarations with the corresponding `@patch` code. | 104 // Merge `external` declarations with the corresponding `@patch` code. |
| 107 var libraryUnit = null; | 105 bool first = true; |
| 108 for (var partContent in partsContents) { | 106 for (var partContent in partsContents) { |
| 109 var partEdits = new StringEditBuffer(partContent); | 107 var partEdits = new StringEditBuffer(partContent); |
| 110 var partUnit = parseCompilationUnit(partContent); | 108 var partUnit = parseCompilationUnit(partContent); |
| 111 if (libraryUnit == null) libraryUnit = partUnit; | 109 partUnit.accept(new PatchApplier(partEdits, patchFinder)); |
| 112 partUnit.accept(new PatchApplier(partEdits, patchInfo, patchContents)); | |
| 113 results.add(partEdits); | 110 results.add(partEdits); |
| 114 } | 111 } |
| 115 | |
| 116 // Add code from the patch that isn't `@patch` | |
| 117 _mergeUnpatched(results[0], libraryUnit, patchUnit, patchContents); | |
| 118 return new List<String>.from(results.map((e) => e.toString())); | 112 return new List<String>.from(results.map((e) => e.toString())); |
| 119 } | 113 } |
| 120 | 114 |
| 121 /// Merges directives and declarations that are not `@patch` into the library. | 115 /// Merge `@patch` declarations into `external` declarations. |
| 122 void _mergeUnpatched(StringEditBuffer edits, CompilationUnit lib, | 116 class PatchApplier extends GeneralizingAstVisitor { |
| 123 CompilationUnit patchUnit, String patchContents) { | 117 final StringEditBuffer edits; |
| 118 final PatchFinder patch; |
| 124 | 119 |
| 125 // Merge directives from the patch | 120 bool _isLibrary = true; // until proven otherwise. |
| 126 // TODO(jmesserly): remove duplicate imports | 121 |
| 127 var directivePos = lib.directives.last.end; | 122 PatchApplier(this.edits, this.patch); |
| 128 for (var directive in patchUnit.directives) { | 123 |
| 129 var uri = directive.uri.stringValue; | 124 @override visitCompilationUnit(CompilationUnit node) { |
| 130 // TODO(jmesserly): figure out what to do about these | 125 super.visitCompilationUnit(node); |
| 131 if (uri.startsWith('dart:_') && uri != 'dart:_internal') continue; | 126 if (_isLibrary) _mergeUnpatched(node); |
| 132 var code = patchContents.substring(directive.offset, directive.end); | |
| 133 edits.insert(directivePos, '\n' + code); | |
| 134 } | 127 } |
| 135 | 128 |
| 136 // Merge declarations from the patch | 129 /// Merges directives and declarations that are not `@patch` into the library. |
| 137 var declarationPos = edits.original.length; | 130 void _mergeUnpatched(CompilationUnit unit) { |
| 138 for (var declaration in patchUnit.declarations) { | 131 // Merge directives from the patch |
| 139 if (_isPatch(declaration)) continue; | 132 // TODO(jmesserly): remove duplicate imports |
| 140 var code = patchContents.substring(declaration.offset, declaration.end); | 133 var directivePos = unit.directives.last.end; |
| 141 edits.insert(declarationPos, '\n' + code); | 134 for (var directive in patch.unit.directives) { |
| 135 var uri = directive.uri.stringValue; |
| 136 // TODO(jmesserly): figure out what to do about these |
| 137 if (uri.startsWith('dart:_') && uri != 'dart:_internal') continue; |
| 138 var code = patch.contents.substring(directive.offset, directive.end); |
| 139 edits.insert(directivePos, '\n' + code); |
| 140 } |
| 141 |
| 142 // Merge declarations from the patch |
| 143 var declarationPos = edits.original.length; |
| 144 for (var declaration in patch.mergeDeclarations) { |
| 145 var code = patch.contents.substring(declaration.offset, declaration.end); |
| 146 edits.insert(declarationPos, '\n' + code); |
| 147 } |
| 142 } | 148 } |
| 143 } | |
| 144 | 149 |
| 145 /// Merge `@patch` declarations into `external` declarations. | 150 @override visitPartOfDirective(PartOfDirective node) { |
| 146 class PatchApplier extends RecursiveAstVisitor { | 151 _isLibrary = false; |
| 147 final StringEditBuffer edits; | 152 } |
| 148 final Map<String, Declaration> patches; | |
| 149 final String patchCode; | |
| 150 | |
| 151 PatchApplier(this.edits, this.patches, this.patchCode); | |
| 152 | 153 |
| 153 @override visitFunctionDeclaration(FunctionDeclaration node) { | 154 @override visitFunctionDeclaration(FunctionDeclaration node) { |
| 154 _maybePatch(node); | 155 _maybePatch(node); |
| 155 return super.visitFunctionDeclaration(node); | |
| 156 } | 156 } |
| 157 @override visitMethodDeclaration(MethodDeclaration node) { | 157 |
| 158 _maybePatch(node); | 158 /// Merge patches and extensions into the class |
| 159 return super.visitMethodDeclaration(node); | 159 @override visitClassDeclaration(ClassDeclaration node) { |
| 160 } | 160 node.members.forEach(_maybePatch); |
| 161 @override visitConstructorDeclaration(ConstructorDeclaration node) { | 161 |
| 162 _maybePatch(node); | 162 var mergeMembers = patch.mergeMembers[_qualifiedName(node)]; |
| 163 return super.visitConstructorDeclaration(node); | 163 if (mergeMembers == null) return; |
| 164 |
| 165 // Merge members from the patch |
| 166 var pos = node.members.last.end; |
| 167 for (var member in mergeMembers) { |
| 168 var code = patch.contents.substring(member.offset, member.end); |
| 169 edits.insert(pos, '\n\n ' + code); |
| 170 } |
| 164 } | 171 } |
| 165 | 172 |
| 166 void _maybePatch(AstNode node) { | 173 void _maybePatch(AstNode node) { |
| 174 if (node is FieldDeclaration) return; |
| 175 |
| 167 var externalKeyword = (node as dynamic).externalKeyword; | 176 var externalKeyword = (node as dynamic).externalKeyword; |
| 168 if (externalKeyword == null) return; | 177 if (externalKeyword == null) return; |
| 169 | 178 |
| 170 var name = _qualifiedName(node); | 179 var name = _qualifiedName(node); |
| 171 var patchNode = patches[name]; | 180 var patchNode = patch.patches[name]; |
| 172 if (patchNode == null) throw 'patch not found for $name: $node'; | 181 if (patchNode == null) throw 'patch not found for $name: $node'; |
| 173 | 182 |
| 174 Annotation patch = patchNode.metadata.lastWhere(_isPatchAnnotation); | 183 Annotation patchMeta = patchNode.metadata.lastWhere(_isPatchAnnotation); |
| 175 var code = patchCode.substring(patch.endToken.next.offset, patchNode.end); | 184 int start = patchMeta.endToken.next.offset; |
| 185 var code = patch.contents.substring(start, patchNode.end); |
| 176 | 186 |
| 177 // For some node like static fields, the node's offset doesn't include | 187 // For some node like static fields, the node's offset doesn't include |
| 178 // the external keyword. Also starting from the keyword lets us preserve | 188 // the external keyword. Also starting from the keyword lets us preserve |
| 179 // documentation comments. | 189 // documentation comments. |
| 180 edits.replace(externalKeyword.offset, node.end, code); | 190 edits.replace(externalKeyword.offset, node.end, code); |
| 181 } | 191 } |
| 182 } | 192 } |
| 183 | 193 |
| 184 class PatchFinder extends RecursiveAstVisitor { | 194 class PatchFinder extends GeneralizingAstVisitor { |
| 195 final String contents; |
| 196 final CompilationUnit unit; |
| 197 |
| 185 final Map patches = <String, Declaration>{}; | 198 final Map patches = <String, Declaration>{}; |
| 199 final Map mergeMembers = <String, List<ClassMember>>{}; |
| 200 final List mergeDeclarations = <CompilationUnitMember>[]; |
| 201 |
| 202 PatchFinder.parseAndVisit(String contents) |
| 203 : contents = contents, |
| 204 unit = parseCompilationUnit(contents) { |
| 205 visitCompilationUnit(unit); |
| 206 } |
| 207 |
| 208 @override visitCompilationUnitMember(CompilationUnitMember node) { |
| 209 mergeDeclarations.add(node); |
| 210 } |
| 211 |
| 212 @override visitClassDeclaration(ClassDeclaration node) { |
| 213 if (_isPatch(node)) { |
| 214 var members = <ClassMember>[]; |
| 215 for (var member in node.members) { |
| 216 if (_isPatch(member)) { |
| 217 patches[_qualifiedName(member)] = member; |
| 218 } else { |
| 219 members.add(member); |
| 220 } |
| 221 } |
| 222 if (members.isNotEmpty) { |
| 223 mergeMembers[_qualifiedName(node)] = members; |
| 224 } |
| 225 } else { |
| 226 mergeDeclarations.add(node); |
| 227 } |
| 228 } |
| 186 | 229 |
| 187 @override visitFunctionDeclaration(FunctionDeclaration node) { | 230 @override visitFunctionDeclaration(FunctionDeclaration node) { |
| 188 _maybeStorePatch(node); | 231 if (_isPatch(node)) { |
| 189 return super.visitFunctionDeclaration(node); | 232 patches[_qualifiedName(node)] = node; |
| 190 } | 233 } else { |
| 191 @override visitMethodDeclaration(MethodDeclaration node) { | 234 mergeDeclarations.add(node); |
| 192 _maybeStorePatch(node); | 235 } |
| 193 return super.visitMethodDeclaration(node); | |
| 194 } | |
| 195 @override visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 196 _maybeStorePatch(node); | |
| 197 return super.visitConstructorDeclaration(node); | |
| 198 } | 236 } |
| 199 | 237 |
| 200 void _maybeStorePatch(Declaration node) { | 238 @override visitFunctionBody(node) {} // skip method bodies |
| 201 if (!_isPatch(node)) return; | |
| 202 | |
| 203 var parent = node.parent; | |
| 204 if (parent is ClassDeclaration) { | |
| 205 if (!_isPatch(parent)) throw 'class $parent is not a patch but $node is'; | |
| 206 } | |
| 207 | |
| 208 patches[_qualifiedName(node)] = node; | |
| 209 } | |
| 210 } | 239 } |
| 211 | 240 |
| 212 String _qualifiedName(Declaration node) { | 241 String _qualifiedName(Declaration node) { |
| 213 assert(node is MethodDeclaration || | 242 assert(node is MethodDeclaration || |
| 214 node is FunctionDeclaration || | 243 node is FunctionDeclaration || |
| 215 node is ConstructorDeclaration); | 244 node is ConstructorDeclaration); |
| 216 | 245 |
| 217 var parent = node.parent; | 246 var parent = node.parent; |
| 218 var className = ''; | 247 var className = ''; |
| 219 if (parent is ClassDeclaration) { | 248 if (parent is ClassDeclaration) { |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 313 int get length => end - begin; | 342 int get length => end - begin; |
| 314 | 343 |
| 315 String toString() => '(Edit @ $begin,$end: "$replace")'; | 344 String toString() => '(Edit @ $begin,$end: "$replace")'; |
| 316 | 345 |
| 317 int compareTo(_StringEdit other) { | 346 int compareTo(_StringEdit other) { |
| 318 int diff = begin - other.begin; | 347 int diff = begin - other.begin; |
| 319 if (diff != 0) return diff; | 348 if (diff != 0) return diff; |
| 320 return end - other.end; | 349 return end - other.end; |
| 321 } | 350 } |
| 322 } | 351 } |
| OLD | NEW |