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

Side by Side Diff: pkg/stub_core_library/lib/stub_core_library.dart

Issue 1134583005: Remove the core library stubbing infrastructure. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 7 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 | Annotate | Revision Log
« no previous file with comments | « pkg/stub_core_library/lib/src/utils.dart ('k') | pkg/stub_core_library/pubspec.yaml » ('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) 2014, 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 library stub_core_library;
6
7 import 'package:analyzer/analyzer.dart';
8 import 'package:analyzer/src/generated/java_core.dart';
9 import 'package:analyzer/src/generated/scanner.dart';
10 import 'package:path/path.dart' as p;
11
12 /// Returns the contents of a stub version of the library at [path].
13 ///
14 /// A stub library has the same API as the original library, but none of the
15 /// implementation. Specifically, this guarantees that any code that worked with
16 /// the original library will be statically valid with the stubbed library, and
17 /// its only runtime errors will be [UnsupportedError]s. This means that
18 /// constants and const constructors are preserved.
19 ///
20 /// [importReplacements] is a map from import URIs to their replacements. It's
21 /// used so that mutliple interrelated libraries can refer to their stubbed
22 /// versions rather than the originals.
23 String stubFile(String path, [Map<String, String> importReplacements]) {
24 var visitor = new _StubVisitor(path, importReplacements);
25 parseDartFile(path).accept(visitor);
26 return visitor.toString();
27 }
28
29 /// Returns the contents of a stub version of the library parsed from [code].
30 ///
31 /// If [code] contains `part` directives, they will be resolved relative to
32 /// [path]. The contents of the parted files will be stubbed and inlined.
33 String stubCode(String code, String path,
34 [Map<String, String> importReplacements]) {
35 var visitor = new _StubVisitor(path, importReplacements);
36 parseCompilationUnit(code, name: path).accept(visitor);
37 return visitor.toString();
38 }
39
40 /// An AST visitor that traverses the tree of the original library and writes
41 /// the stubbed version.
42 ///
43 /// In order to avoid complex tree-shaking logic, this takes a conservative
44 /// approach to removing private code. Private classes may still be extended by
45 /// public classes; private constants may be referenced by public constants; and
46 /// private static and top-level methods may be referenced by public constants
47 /// or by superclass constructor calls. All of these are preserved even though
48 /// most could theoretically be eliminated.
49 class _StubVisitor extends ToSourceVisitor {
50 /// The directory containing the library being visited.
51 final String _root;
52
53 /// Which imports to replace.
54 final Map<String, String> _importReplacements;
55
56 final PrintStringWriter _writer;
57
58 // TODO(nweiz): Get rid of this when issue 19897 is fixed.
59 /// The current class declaration being visited.
60 ///
61 /// This is `null` if there is no current class declaration.
62 ClassDeclaration _class;
63
64 _StubVisitor(String path, Map<String, String> importReplacements)
65 : this._(path, importReplacements, new PrintStringWriter());
66
67 _StubVisitor._(String path, Map<String, String> importReplacements,
68 PrintStringWriter writer)
69 : _root = p.dirname(path),
70 _importReplacements = importReplacements == null ? const {} :
71 importReplacements,
72 _writer = writer,
73 super(writer);
74
75 String toString() => _writer.toString();
76
77 visitImportDirective(ImportDirective node) {
78 node = _modifyDirective(node);
79 if (node != null) super.visitImportDirective(node);
80 }
81
82 visitExportDirective(ExportDirective node) {
83 node = _modifyDirective(node);
84 if (node != null) super.visitExportDirective(node);
85 }
86
87 visitPartDirective(PartDirective node) {
88 // Inline parts directly in the output file.
89 var path = p.url.join(_root, p.fromUri(node.uri.stringValue));
90 parseDartFile(path).accept(new _StubVisitor._(path, const {}, _writer));
91 }
92
93 visitPartOfDirective(PartOfDirective node) {
94 // Remove "part of", since parts are inlined.
95 }
96
97 visitClassDeclaration(ClassDeclaration node) {
98 _class = _clone(node);
99 _class.nativeClause = null;
100 super.visitClassDeclaration(_class);
101 _class = null;
102 }
103
104 visitConstructorDeclaration(ConstructorDeclaration node) {
105 node = _withoutExternal(node);
106
107 // Remove field initializers and redirecting initializers but not superclass
108 // initializers. The code is ugly because NodeList doesn't support
109 // removeWhere.
110 var superclassInitializers = node.initializers.where((initializer) =>
111 initializer is SuperConstructorInvocation).toList();
112 node.initializers.clear();
113 node.initializers.addAll(superclassInitializers);
114
115 // Add a space because ToSourceVisitor doesn't and it makes testing easier.
116 _writer.print(" ");
117 super.visitConstructorDeclaration(node);
118 }
119
120 visitSuperConstructorInvocation(SuperConstructorInvocation node) {
121 // If this is a const constructor, it should actually work, so don't screw
122 // with the superclass constructor.
123 if ((node.parent as ConstructorDeclaration).constKeyword != null) {
124 return super.visitSuperConstructorInvocation(node);
125 }
126
127 _writer.print("super");
128 _visitNodeWithPrefix(".", node.constructorName);
129 _writer.print("(");
130
131 // If one stubbed class extends another, we don't want to run the original
132 // code for the superclass constructor call, and we do want an
133 // UnsupportedException that points to the subclass rather than the
134 // superclass. To do this, we null out all but the first superclass
135 // constructor parameter and replace the first parameter with a throw.
136 var positionalArguments = node.argumentList.arguments
137 .where((argument) => argument is! NamedExpression);
138 if (positionalArguments.isNotEmpty) {
139 _writer.print(_unsupported(_functionName(node)));
140 for (var i = 0; i < positionalArguments.length - 1; i++) {
141 _writer.print(", null");
142 }
143 }
144
145 _writer.print(")");
146 }
147
148 visitMethodDeclaration(MethodDeclaration node) {
149 // Private non-static methods aren't public and aren't accessible from
150 // constant expressions, so can be safely removed.
151 if (Identifier.isPrivateName(node.name.name) && !node.isStatic) return;
152 _writer.print(" ");
153 super.visitMethodDeclaration(_withoutExternal(node));
154 }
155
156 visitFunctionDeclaration(FunctionDeclaration node) {
157 super.visitFunctionDeclaration(_withoutExternal(node));
158 }
159
160 visitBlockFunctionBody(BlockFunctionBody node) => _emitFunctionBody(node);
161
162 visitExpressionFunctionBody(ExpressionFunctionBody node) =>
163 _emitFunctionBody(node);
164
165 visitNativeFunctionBody(NativeFunctionBody node) => _emitFunctionBody(node);
166
167 visitEmptyFunctionBody(FunctionBody node) {
168 // Preserve empty function bodies for abstract methods, since there's no
169 // reason not to. Note that "empty" here means "foo();" not "foo() {}".
170 var isAbstractMethod = node.parent is MethodDeclaration &&
171 !(node.parent as MethodDeclaration).isStatic && _class != null &&
172 _class.isAbstract;
173
174 // Preserve empty function bodies for const constructors because we want
175 // them to continue to work.
176 var isConstConstructor = node.parent is ConstructorDeclaration &&
177 (node.parent as ConstructorDeclaration).constKeyword != null;
178
179 if (isAbstractMethod || isConstConstructor) {
180 super.visitEmptyFunctionBody(node);
181 _writer.print(" ");
182 } else {
183 _writer.print(" ");
184 _emitFunctionBody(node);
185 }
186 }
187
188 visitFieldFormalParameter(FieldFormalParameter node) {
189 // Remove "this." because instance variables are replaced with getters and
190 // setters or just set to null.
191 _emitTokenWithSuffix(node.keyword, " ");
192
193 // Make sure the parameter is still typed by grabbing the type from the
194 // associated instance variable.
195 var type = node.type;
196 if (type == null) {
197 var variable = _class.members
198 .where((member) => member is FieldDeclaration)
199 .expand((member) => member.fields.variables)
200 .firstWhere((variable) => variable.name.name == node.identifier.name,
201 orElse: () => null);
202 if (variable != null) type = variable.parent.type;
203 }
204
205 _visitNodeWithSuffix(type, " ");
206 _visitNode(node.identifier);
207 _visitNode(node.parameters);
208 }
209
210 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
211 node.variables.variables.forEach(_emitVariableDeclaration);
212 }
213
214 visitFieldDeclaration(FieldDeclaration node) {
215 _writer.print(" ");
216 node.fields.variables.forEach(_emitVariableDeclaration);
217 }
218
219 /// Modifies a directive to respect [importReplacements] and ignore hidden
220 /// core libraries.
221 ///
222 /// This can return `null`, indicating that the directive should not be
223 /// emitted.
224 UriBasedDirective _modifyDirective(UriBasedDirective node) {
225 // Ignore internal "dart:" libraries.
226 if (node.uri.stringValue.startsWith('dart:_')) return null;
227
228 // Replace libraries in [importReplacements].
229 if (_importReplacements.containsKey(node.uri.stringValue)) {
230 node = _clone(node);
231 var token = new StringToken(TokenType.STRING,
232 '"${_importReplacements[node.uri.stringValue]}"', 0);
233 node.uri = new SimpleStringLiteral(token, null);
234 }
235
236 return node;
237 }
238
239 /// Emits a variable declaration, either as a literal variable or as a getter
240 /// and maybe a setter that throw [UnsupportedError]s.
241 _emitVariableDeclaration(VariableDeclaration node) {
242 VariableDeclarationList parent = node.parent;
243 var isStatic = node.parent.parent is FieldDeclaration &&
244 (node.parent.parent as FieldDeclaration).isStatic;
245
246 // Preserve constants as-is.
247 if (node.isConst) {
248 if (isStatic) _writer.print("static ");
249 _writer.print("const ");
250 _visitNode(node);
251 _writer.print("; ");
252 return;
253 }
254
255 // Ignore non-const private variables.
256 if (Identifier.isPrivateName(node.name.name)) return;
257
258 // There's no need to throw errors for instance fields of classes that can't
259 // be constructed.
260 if (!isStatic && _class != null && !_inConstructableClass) {
261 _emitTokenWithSuffix(parent.keyword, " ");
262 _visitNodeWithSuffix(parent.type, " ");
263 _visitNode(node.name);
264 // Add an initializer to make sure that final variables are initialized.
265 if (node.isFinal) _writer.print(" = null; ");
266 return;
267 }
268
269 var name = node.name.name;
270 if (_class != null) name = "${_class.name}.$name";
271
272 // Convert public variables into getters and setters that throw
273 // UnsupportedErrors.
274 if (isStatic) _writer.print("static ");
275 _visitNodeWithSuffix(parent.type, " ");
276 _writer.print("get ");
277 _visitNode(node.name);
278 _writer.print(" => ${_unsupported(name)}; ");
279 if (node.isFinal) return;
280
281 if (isStatic) _writer.print("static ");
282 _writer.print("set ");
283 _visitNode(node.name);
284 _writer.print("(");
285 _visitNodeWithSuffix(parent.type, " ");
286 _writer.print("_) { ${_unsupported("$name=")}; } ");
287 }
288
289 /// Emits a function body.
290 ///
291 /// This usually emits a body that throws an [UnsupportedError], but it can
292 /// emit an empty body as well.
293 void _emitFunctionBody(FunctionBody node) {
294 // There's no need to throw errors for instance methods of classes that
295 // can't be constructed.
296 var parent = node.parent;
297 if (parent is MethodDeclaration && !parent.isStatic &&
298 !_inConstructableClass) {
299 _writer.print('{} ');
300 return;
301 }
302
303 _writer.print('{ ${_unsupported(_functionName(node))}; } ');
304 }
305
306 // Returns a human-readable name for the function containing [node].
307 String _functionName(AstNode node) {
308 // Come up with a nice name for the error message so users can tell exactly
309 // what unsupported method they're calling.
310 var function = node.getAncestor((ancestor) =>
311 ancestor is FunctionDeclaration || ancestor is MethodDeclaration);
312 if (function != null) {
313 var name = function.name.name;
314 if (function.isSetter) {
315 name = "$name=";
316 } else if (!function.isGetter &&
317 !(function is MethodDeclaration && function.isOperator)) {
318 name = "$name()";
319 }
320 if (_class != null) name = "${_class.name}.$name";
321 return name;
322 }
323
324 var constructor = node.getAncestor((ancestor) =>
325 ancestor is ConstructorDeclaration);
326 if (constructor == null) return "This function";
327
328 var name = "new ${constructor.returnType.name}";
329 if (constructor.name != null) name = "$name.${constructor.name}";
330 return "$name()";
331 }
332
333 /// Returns a deep copy of [node].
334 AstNode _clone(AstNode node) => node.accept(new AstCloner());
335
336 /// Returns a deep copy of [node] without the "external" keyword.
337 AstNode _withoutExternal(node) {
338 var clone = node.accept(new AstCloner());
339 clone.externalKeyword = null;
340 return clone;
341 }
342
343 /// Visits [node] if it's non-`null`.
344 void _visitNode(AstNode node) {
345 if (node != null) node.accept(this);
346 }
347
348 /// Visits [node] then emits [suffix] if [node] isn't `null`.
349 void _visitNodeWithSuffix(AstNode node, String suffix) {
350 if (node == null) return;
351 node.accept(this);
352 _writer.print(suffix);
353 }
354
355 /// Emits [prefix] then visits [node] if [node] isn't `null`.
356 void _visitNodeWithPrefix(String prefix, AstNode node) {
357 if (node == null) return;
358 _writer.print(prefix);
359 node.accept(this);
360 }
361
362 /// Emits [token] followed by [suffix] if [token] isn't `null`.
363 void _emitTokenWithSuffix(Token token, String suffix) {
364 if (token == null) return;
365 _writer.print(token.lexeme);
366 _writer.print(suffix);
367 }
368
369 /// Returns an expression that throws an [UnsupportedError] explaining that
370 /// [name] isn't supported.
371 String _unsupported(String name) => 'throw new UnsupportedError("$name is '
372 'unsupported on this platform.")';
373
374 /// Returns whether or not the visitor is currently visiting a class that can
375 /// be constructed without error after it's stubbed.
376 ///
377 /// There are two cases where a class will be constructable once it's been
378 /// stubbed. First, a class with a const constructor will be preserved, since
379 /// making the const constructor fail would statically break code. Second, a
380 /// class with a default constructor is preserved since adding a constructor
381 /// that throws an error could statically break uses of the class as a mixin.
382 bool get _inConstructableClass {
383 if (_class == null) return false;
384
385 var constructors = _class.members.where((member) =>
386 member is ConstructorDeclaration);
387 if (constructors.isEmpty) return true;
388
389 return constructors.any((constructor) => constructor.constKeyword != null);
390 }
391 }
OLDNEW
« no previous file with comments | « pkg/stub_core_library/lib/src/utils.dart ('k') | pkg/stub_core_library/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698