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

Side by Side Diff: pkg/analysis_server/lib/src/analysis_server.dart

Issue 308003009: Add ServerOperation and queue. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Changes for review comments Created 6 years, 6 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
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library analysis.server; 5 library analysis.server;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import 'package:analysis_server/src/analysis_logger.dart'; 9 import 'package:analysis_server/src/analysis_logger.dart';
10 import 'package:analysis_server/src/channel.dart'; 10 import 'package:analysis_server/src/channel.dart';
11 import 'package:analysis_server/src/constants.dart'; 11 import 'package:analysis_server/src/constants.dart';
12 import 'package:analysis_server/src/context_directory_manager.dart'; 12 import 'package:analysis_server/src/context_directory_manager.dart';
13 import 'package:analysis_server/src/domain_analysis.dart'; 13 import 'package:analysis_server/src/domain_analysis.dart';
14 import 'package:analysis_server/src/operation/operation.dart';
15 import 'package:analysis_server/src/operation/operation_queue.dart';
14 import 'package:analysis_server/src/protocol.dart'; 16 import 'package:analysis_server/src/protocol.dart';
15 import 'package:analysis_server/src/resource.dart'; 17 import 'package:analysis_server/src/resource.dart';
16 import 'package:analyzer/src/generated/ast.dart'; 18 import 'package:analyzer/src/generated/ast.dart';
17 import 'package:analyzer/src/generated/engine.dart'; 19 import 'package:analyzer/src/generated/engine.dart';
18 import 'package:analyzer/src/generated/error.dart'; 20 import 'package:analyzer/src/generated/error.dart';
19 import 'package:analyzer/src/generated/java_core.dart'; 21 import 'package:analyzer/src/generated/java_core.dart';
20 import 'package:analyzer/src/generated/source.dart'; 22 import 'package:analyzer/src/generated/source.dart';
21 import 'package:analyzer/src/generated/sdk.dart'; 23 import 'package:analyzer/src/generated/sdk.dart';
22 import 'package:analyzer/src/generated/sdk_io.dart'; 24 import 'package:analyzer/src/generated/sdk_io.dart';
23 import 'package:analyzer/src/generated/source_io.dart'; 25 import 'package:analyzer/src/generated/source_io.dart';
24 import 'package:analysis_server/src/computers.dart'; 26 import 'package:analysis_server/src/computers.dart';
25 27
26 28
27 /** 29 /**
28 * An instance of [DirectoryBasedDartSdk] that is shared between 30 * An instance of [DirectoryBasedDartSdk] that is shared between
29 * [AnalysisServer] instances to improve performance. 31 * [AnalysisServer] instances to improve performance.
30 */ 32 */
31 final DirectoryBasedDartSdk SHARED_SDK = DirectoryBasedDartSdk.defaultSdk; 33 final DirectoryBasedDartSdk SHARED_SDK = DirectoryBasedDartSdk.defaultSdk;
32 34
33 class AnalysisServerContextDirectoryManager extends ContextDirectoryManager { 35 class AnalysisServerContextDirectoryManager extends ContextDirectoryManager {
34 final AnalysisServer analysisServer; 36 final AnalysisServer analysisServer;
35 37
36 AnalysisServerContextDirectoryManager(this.analysisServer, ResourceProvider re sourceProvider) 38 AnalysisServerContextDirectoryManager(this.analysisServer, ResourceProvider re sourceProvider)
37 : super(resourceProvider); 39 : super(resourceProvider);
38 40
39 void addContext(Folder folder, File pubspecFile) { 41 void addContext(Folder folder, File pubspecFile) {
40 ContextDirectory contextDirectory = new ContextDirectory( 42 ContextDirectory contextDirectory = new ContextDirectory(
41 analysisServer.defaultSdk, folder, pubspecFile); 43 analysisServer.defaultSdk, folder, pubspecFile);
42 analysisServer.folderMap[folder] = contextDirectory; 44 analysisServer.folderMap[folder] = contextDirectory;
43 analysisServer.addContextToWorkQueue(contextDirectory.context); 45 analysisServer.schedulePerformAnalysisOperation(contextDirectory.context);
44 } 46 }
45 47
46 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) { 48 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) {
47 analysisServer.folderMap[contextFolder].context.applyChanges(changeSet); 49 analysisServer.folderMap[contextFolder].context.applyChanges(changeSet);
48 } 50 }
49 } 51 }
50 52
51 /** 53 /**
52 * Instances of the class [AnalysisServer] implement a server that listens on a 54 * Instances of the class [AnalysisServer] implement a server that listens on a
53 * [CommunicationChannel] for analysis requests and process them. 55 * [CommunicationChannel] for analysis requests and process them.
54 */ 56 */
55 class AnalysisServer { 57 class AnalysisServer {
56 /** 58 /**
57 * The channel from which requests are received and to which responses should 59 * The channel from which requests are received and to which responses should
58 * be sent. 60 * be sent.
59 */ 61 */
60 final ServerCommunicationChannel channel; 62 final ServerCommunicationChannel channel;
61 63
62 /** 64 /**
63 * [ContextDirectoryManager] which handles the mapping from analysis roots 65 * [ContextDirectoryManager] which handles the mapping from analysis roots
64 * to context directories. 66 * to context directories.
65 */ 67 */
66 AnalysisServerContextDirectoryManager contextDirectoryManager; 68 AnalysisServerContextDirectoryManager contextDirectoryManager;
67 69
68 /** 70 /**
69 * A flag indicating whether the server is running. When false, contexts 71 * A flag indicating whether the server is running. When false, contexts
70 * will no longer be added to [contextWorkQueue], and [performTask] will 72 * will no longer be added to [contextWorkQueue], and [performOperation] will
71 * discard any tasks it finds on [contextWorkQueue]. 73 * discard any tasks it finds on [contextWorkQueue].
72 */ 74 */
73 bool running; 75 bool running;
74 76
75 /** 77 /**
76 * A list of the request handlers used to handle the requests sent to this 78 * A list of the request handlers used to handle the requests sent to this
77 * server. 79 * server.
78 */ 80 */
79 List<RequestHandler> handlers; 81 List<RequestHandler> handlers;
80 82
81 // TODO(scheglov) remove once setAnalysisRoots() is completely implemented
82 // /**
83 // * A table mapping context id's to the analysis contexts associated with the m.
84 // */
85 // final Map<String, AnalysisContext> contextMap = new Map<String, AnalysisCont ext>();
86 //
87 // /**
88 // * A table mapping analysis contexts to the context id's associated with the m.
89 // */
90 // final Map<AnalysisContext, String> contextIdMap = new Map<AnalysisContext, S tring>();
91
92 /** 83 /**
93 * The current default [DartSdk]. 84 * The current default [DartSdk].
94 */ 85 */
95 DartSdk defaultSdk = SHARED_SDK; 86 DartSdk defaultSdk = SHARED_SDK;
96 87
97 /** 88 /**
98 * A table mapping [Folder]s to the [ContextDirectory]s associated with them. 89 * A table mapping [Folder]s to the [ContextDirectory]s associated with them.
99 */ 90 */
100 final Map<Folder, ContextDirectory> folderMap = <Folder, ContextDirectory>{}; 91 final Map<Folder, ContextDirectory> folderMap = <Folder, ContextDirectory>{};
101 92
102 /** 93 /**
103 * The context identifier used in the last status notification. 94 * A queue of the operations to perform in this server.
95 *
96 * Invariant: when this queue is non-empty, there is exactly one pending call
97 * to [performOperation] on the event queue. When this list is empty, there a re
98 * no calls to [performOperation] on the event queue.
104 */ 99 */
105 String lastStatusNotificationContextId = null; 100 ServerOperationQueue operationQueue;
106
107 /**
108 * A list of the analysis contexts for which analysis work needs to be
109 * performed.
110 *
111 * Invariant: when this list is non-empty, there is exactly one pending call
112 * to [performTask] on the event queue. When this list is empty, there are
113 * no calls to [performTask] on the event queue.
114 */
115 final List<AnalysisContext> contextWorkQueue = new List<AnalysisContext>();
116 101
117 /** 102 /**
118 * A set of the [ServerService]s to send notifications for. 103 * A set of the [ServerService]s to send notifications for.
119 */ 104 */
120 Set<ServerService> serverServices = new Set<ServerService>(); 105 Set<ServerService> serverServices = new Set<ServerService>();
121 106
122 /** 107 /**
123 * A table mapping [AnalysisService]s to the file paths for which these 108 * A table mapping [AnalysisService]s to the file paths for which these
124 * notifications should be sent. 109 * notifications should be sent.
125 */ 110 */
126 Map<AnalysisService, Set<String>> analysisServices = <AnalysisService, Set<Str ing>>{}; 111 Map<AnalysisService, Set<String>> analysisServices = <AnalysisService, Set<Str ing>>{};
127 112
128 /** 113 /**
129 * Initialize a newly created server to receive requests from and send 114 * Initialize a newly created server to receive requests from and send
130 * responses to the given [channel]. 115 * responses to the given [channel].
131 */ 116 */
132 AnalysisServer(this.channel, ResourceProvider resourceProvider) { 117 AnalysisServer(this.channel, ResourceProvider resourceProvider) {
118 operationQueue = new ServerOperationQueue(this);
133 contextDirectoryManager = new AnalysisServerContextDirectoryManager(this, re sourceProvider); 119 contextDirectoryManager = new AnalysisServerContextDirectoryManager(this, re sourceProvider);
134 AnalysisEngine.instance.logger = new AnalysisLogger(); 120 AnalysisEngine.instance.logger = new AnalysisLogger();
135 running = true; 121 running = true;
136 Notification notification = new Notification(NOTIFICATION_CONNECTED); 122 Notification notification = new Notification(NOTIFICATION_CONNECTED);
137 channel.sendNotification(notification); 123 channel.sendNotification(notification);
138 channel.listen(handleRequest, onDone: done, onError: error); 124 channel.listen(handleRequest, onDone: done, onError: error);
139 } 125 }
140 126
141 /** 127 /**
142 * If [running] is true, add the given [context] to the list of analysis 128 * Schedules analysis of the given context.
143 * contexts for which analysis work needs to be performed, and ensure that
144 * the work will be performed.
145 */ 129 */
146 void addContextToWorkQueue(AnalysisContext context) { 130 void schedulePerformAnalysisOperation(AnalysisContext context) {
147 if (!running) { 131 scheduleOperation(new PerformAnalysisOperation(context, false));
148 return; 132 }
149 } 133
150 if (!contextWorkQueue.contains(context)) { 134 /**
151 contextWorkQueue.add(context); 135 * Schedules execution of the given [ServerOperation].
152 if (contextWorkQueue.length == 1) { 136 */
153 // Work queue was previously empty, so schedule analysis. 137 void scheduleOperation(ServerOperation operation) {
154 _scheduleTask(); 138 bool wasEmpty = operationQueue.isEmpty;
155 } 139 operationQueue.add(operation);
140 if (wasEmpty) {
141 _schedulePerformOperation();
156 } 142 }
157 } 143 }
158 144
159 /** 145 /**
160 * The socket from which requests are being read has been closed. 146 * The socket from which requests are being read has been closed.
161 */ 147 */
162 void done() { 148 void done() {
163 running = false; 149 running = false;
164 } 150 }
165 151
(...skipping 19 matching lines...) Expand all
185 } 171 }
186 } on RequestFailure catch (exception) { 172 } on RequestFailure catch (exception) {
187 channel.sendResponse(exception.response); 173 channel.sendResponse(exception.response);
188 return; 174 return;
189 } 175 }
190 } 176 }
191 channel.sendResponse(new Response.unknownRequest(request)); 177 channel.sendResponse(new Response.unknownRequest(request));
192 } 178 }
193 179
194 /** 180 /**
195 * Perform the next available task. If a request was received that has not yet 181 * Returns `true` if the given [AnalysisContext] is a priority one.
196 * been performed, perform it next. Otherwise, look for some analysis that
197 * needs to be done and do that. Otherwise, do nothing.
198 */ 182 */
199 void performTask() { 183 bool isPriorityContext(AnalysisContext context) {
184 // TODO(scheglov) implement support for priority sources/contexts
185 return false;
186 }
187
188 /**
189 * Perform the next available [ServerOperation].
190 */
191 void performOperation() {
200 if (!running) { 192 if (!running) {
201 // An error has occurred, or the connection to the client has been 193 // An error has occurred, or the connection to the client has been
202 // closed, since performTask() was scheduled on the event queue. So 194 // closed, since this method was scheduled on the event queue. So
203 // don't do any analysis. Instead clear the work queue. 195 // don't do anything. Instead clear the operation queue.
204 contextWorkQueue.clear(); 196 operationQueue.clear();
205 }
206 if (contextWorkQueue.isEmpty) {
207 // Nothing to do.
208 return; 197 return;
209 } 198 }
210 // 199 // prepare next operation
211 // Look for a context that has work to be done and then perform one task. 200 ServerOperation operation = operationQueue.take();
212 // 201 // perform the operation
213 List<ChangeNotice> notices = null;
214 try { 202 try {
215 AnalysisContext context = contextWorkQueue[0]; 203 operation.perform(this);
216 // 204 } catch (e) {
217 // TODO(brianwilkerson) Add an optional function-valued parameter to 205 // TODO(scheglov) decide how to handle exceptions
218 // performAnalysisTask that will be called when the task has been computed
219 // but before it is performed and send notification in the function:
220 //
221 // AnalysisResult result = context.performAnalysisTask((taskDescription) {
222 // sendStatusNotification(context.toString(), taskDescription);
223 // });
224 //
225 sendStatusNotification(context.toString());
226 AnalysisResult result = context.performAnalysisTask();
227 notices = result.changeNotices;
228 } finally { 206 } finally {
229 if (notices == null) { 207 if (!operationQueue.isEmpty) {
230 // Either we have no more work to do for this context, or there was an 208 _schedulePerformOperation();
231 // unhandled exception trying to perform the analysis. In either case,
232 // remove the context form the work queue so we won't try to do more
233 // analysis on it.
234 contextWorkQueue.removeAt(0);
235 } else { 209 } else {
236 sendNotices(notices);
237 }
238 //
239 // Schedule this method to be run again if there is any more work to be
240 // done.
241 //
242 if (contextWorkQueue.isEmpty) {
243 sendStatusNotification(null); 210 sendStatusNotification(null);
Brian Wilkerson 2014/06/02 18:21:03 This isn't optimal, but perhaps we should address
244 } else {
245 _scheduleTask();
246 } 211 }
247 } 212 }
248 } 213 }
249 214
250 /** 215 /**
216 * Perform analysis in the given [AnalysisContext].
217 */
218 void internalPerformAnalysis(AnalysisContext context) {
219 //
220 // TODO(brianwilkerson) Add an optional function-valued parameter to
221 // performAnalysisTask that will be called when the task has been computed
222 // but before it is performed and send notification in the function:
223 //
224 // AnalysisResult result = context.performAnalysisTask((taskDescription) {
225 // sendStatusNotification(context.toString(), taskDescription);
226 // });
227 // prepare results
228 AnalysisResult result = context.performAnalysisTask();
229 List<ChangeNotice> notices = result.changeNotices;
230 if (notices == null) {
231 return;
232 }
233 // TODO(scheglov) remember known sources
234 // TODO(scheglov) index units
235 // TODO(scheglov) schedule notifications
236 if (notices != null) {
Brian Wilkerson 2014/06/02 18:21:03 notices can only be non-null at this point because
scheglov 2014/06/02 18:32:26 Done.
237 sendNotices(notices);
238 } else {
239 sendStatusNotification(null);
240 }
241 // continue analysis
242 operationQueue.add(new PerformAnalysisOperation(context, true));
243 }
244
245 /**
251 * Send the information in the given list of notices back to the client. 246 * Send the information in the given list of notices back to the client.
252 */ 247 */
253 void sendNotices(List<ChangeNotice> notices) { 248 void sendNotices(List<ChangeNotice> notices) {
254 for (int i = 0; i < notices.length; i++) { 249 for (int i = 0; i < notices.length; i++) {
255 ChangeNotice notice = notices[i]; 250 ChangeNotice notice = notices[i];
256 Source source = notice.source; 251 Source source = notice.source;
257 CompilationUnit dartUnit = notice.compilationUnit; 252 CompilationUnit dartUnit = notice.compilationUnit;
258 // TODO(scheglov) use default subscriptions 253 // TODO(scheglov) use default subscriptions
259 String file = source.fullName; 254 String file = source.fullName;
260 if (dartUnit != null) { 255 if (dartUnit != null) {
(...skipping 26 matching lines...) Expand all
287 } 282 }
288 283
289 /** 284 /**
290 * Send status notification to the client. The `contextId` indicates 285 * Send status notification to the client. The `contextId` indicates
291 * the current context being analyzed or `null` if analysis is complete. 286 * the current context being analyzed or `null` if analysis is complete.
292 */ 287 */
293 void sendStatusNotification(String contextId) { 288 void sendStatusNotification(String contextId) {
294 // if (contextId == lastStatusNotificationContextId) { 289 // if (contextId == lastStatusNotificationContextId) {
295 // return; 290 // return;
296 // } 291 // }
297 lastStatusNotificationContextId = contextId; 292 // lastStatusNotificationContextId = contextId;
298 Notification notification = new Notification(NOTIFICATION_STATUS); 293 Notification notification = new Notification(NOTIFICATION_STATUS);
299 Map<String, Object> analysis = new Map(); 294 Map<String, Object> analysis = new Map();
300 if (contextId != null) { 295 if (contextId != null) {
301 analysis['analyzing'] = true; 296 analysis['analyzing'] = true;
302 // TODO(danrubel): replace contextId with real analysisTarget 297 // TODO(danrubel): replace contextId with real analysisTarget
303 analysis['analysisTarget'] = contextId; 298 analysis['analysisTarget'] = contextId;
304 } else { 299 } else {
305 analysis['analyzing'] = false; 300 analysis['analyzing'] = false;
306 } 301 }
307 notification.params['analysis'] = analysis; 302 notification.params['analysis'] = analysis;
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
339 changes.forEach((file, change) { 334 changes.forEach((file, change) {
340 AnalysisContext analysisContext = _getAnalysisContext(file); 335 AnalysisContext analysisContext = _getAnalysisContext(file);
341 if (analysisContext != null) { 336 if (analysisContext != null) {
342 Source source = _getSource(file); 337 Source source = _getSource(file);
343 if (change.offset == null) { 338 if (change.offset == null) {
344 analysisContext.setContents(source, change.content); 339 analysisContext.setContents(source, change.content);
345 } else { 340 } else {
346 analysisContext.setChangedContents(source, change.content, 341 analysisContext.setChangedContents(source, change.content,
347 change.offset, change.oldLength, change.newLength); 342 change.offset, change.oldLength, change.newLength);
348 } 343 }
349 addContextToWorkQueue(analysisContext); 344 schedulePerformAnalysisOperation(analysisContext);
350 } 345 }
351 }); 346 });
352 } 347 }
353 348
354 /** 349 /**
355 * Implementation for `analysis.setSubscriptions`. 350 * Implementation for `analysis.setSubscriptions`.
356 */ 351 */
357 void setAnalysisSubscriptions(Map<AnalysisService, Set<String>> subscriptions) { 352 void setAnalysisSubscriptions(Map<AnalysisService, Set<String>> subscriptions) {
358 // send notifications for already analyzed sources 353 // send notifications for already analyzed sources
359 subscriptions.forEach((service, Set<String> newFiles) { 354 subscriptions.forEach((service, Set<String> newFiles) {
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 Source unitSource = _getSource(path); 408 Source unitSource = _getSource(path);
414 List<Source> librarySources = context.getLibrariesContaining(unitSource); 409 List<Source> librarySources = context.getLibrariesContaining(unitSource);
415 if (librarySources.isEmpty) { 410 if (librarySources.isEmpty) {
416 return null; 411 return null;
417 } 412 }
418 // get a resolved unit 413 // get a resolved unit
419 return context.getResolvedCompilationUnit2(unitSource, librarySources[0]); 414 return context.getResolvedCompilationUnit2(unitSource, librarySources[0]);
420 } 415 }
421 416
422 /** 417 /**
423 * Return `true` if all tasks are finished in this [AnalysisServer]. 418 * Return `true` if all operations have been performed in this [AnalysisServer ].
424 */ 419 */
425 bool test_areTasksFinished() { 420 bool test_areOperationsFinished() {
426 return contextWorkQueue.isEmpty; 421 return operationQueue.isEmpty;
427 } 422 }
428 423
429 static Map<String, Object> errorToJson(AnalysisError analysisError) { 424 static Map<String, Object> errorToJson(AnalysisError analysisError) {
430 // TODO(paulberry): move this function into the AnalysisError class. 425 // TODO(paulberry): move this function into the AnalysisError class.
431 ErrorCode errorCode = analysisError.errorCode; 426 ErrorCode errorCode = analysisError.errorCode;
432 Map<String, Object> result = { 427 Map<String, Object> result = {
433 'file': analysisError.source.fullName, 428 'file': analysisError.source.fullName,
434 // TODO(scheglov) add Enum.fullName ? 429 // TODO(scheglov) add Enum.fullName ?
435 'errorCode': '${errorCode.runtimeType}.${(errorCode as Enum).name}', 430 'errorCode': '${errorCode.runtimeType}.${(errorCode as Enum).name}',
436 'offset': analysisError.offset, 431 'offset': analysisError.offset,
437 'length': analysisError.length, 432 'length': analysisError.length,
438 'message': analysisError.message 433 'message': analysisError.message
439 }; 434 };
440 if (analysisError.correction != null) { 435 if (analysisError.correction != null) {
441 result['correction'] = analysisError.correction; 436 result['correction'] = analysisError.correction;
442 } 437 }
443 return result; 438 return result;
444 } 439 }
445 440
446 /** 441 /**
447 * Send the given [notification] to the client. 442 * Send the given [notification] to the client.
448 */ 443 */
449 void sendNotification(Notification notification) { 444 void sendNotification(Notification notification) {
450 channel.sendNotification(notification); 445 channel.sendNotification(notification);
451 } 446 }
452 447
453 void _scheduleTask() { 448 /**
454 new Future(performTask).catchError((ex, st) { 449 * Schedules [performOperation] exection.
450 */
451 void _schedulePerformOperation() {
452 new Future(performOperation).catchError((ex, st) {
455 AnalysisEngine.instance.logger.logError("${ex}\n${st}"); 453 AnalysisEngine.instance.logger.logError("${ex}\n${st}");
456 }); 454 });
457 } 455 }
458 } 456 }
459 457
460 458
461 /** 459 /**
462 * An enumeration of the services provided by the analysis domain. 460 * An enumeration of the services provided by the analysis domain.
463 */ 461 */
464 class AnalysisService extends Enum2<AnalysisService> { 462 class AnalysisService extends Enum2<AnalysisService> {
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
521 /** 519 /**
522 * An enumeration of the services provided by the server domain. 520 * An enumeration of the services provided by the server domain.
523 */ 521 */
524 class ServerService extends Enum2<ServerService> { 522 class ServerService extends Enum2<ServerService> {
525 static const ServerService STATUS = const ServerService('STATUS', 0); 523 static const ServerService STATUS = const ServerService('STATUS', 0);
526 524
527 static const List<ServerService> VALUES = const [STATUS]; 525 static const List<ServerService> VALUES = const [STATUS];
528 526
529 const ServerService(String name, int ordinal) : super(name, ordinal); 527 const ServerService(String name, int ordinal) : super(name, ordinal);
530 } 528 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/lib/src/domain_context.dart » ('j') | pkg/analysis_server/test/analysis_server_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698