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

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

Issue 1921863002: Listen for changes in the context folder. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 8 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
« no previous file with comments | « no previous file | pkg/analysis_server/test/single_context_manager_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, 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.src.single_context_manager; 5 library analysis_server.src.single_context_manager;
6 6
7 import 'dart:async';
7 import 'dart:core' hide Resource; 8 import 'dart:core' hide Resource;
8 import 'dart:math' as math; 9 import 'dart:math' as math;
9 10
10 import 'package:analysis_server/src/context_manager.dart'; 11 import 'package:analysis_server/src/context_manager.dart';
11 import 'package:analyzer/file_system/file_system.dart'; 12 import 'package:analyzer/file_system/file_system.dart';
12 import 'package:analyzer/source/path_filter.dart'; 13 import 'package:analyzer/source/path_filter.dart';
13 import 'package:analyzer/src/generated/engine.dart'; 14 import 'package:analyzer/src/generated/engine.dart';
14 import 'package:analyzer/src/generated/sdk.dart'; 15 import 'package:analyzer/src/generated/sdk.dart';
15 import 'package:analyzer/src/generated/source.dart'; 16 import 'package:analyzer/src/generated/source.dart';
16 import 'package:analyzer/src/util/glob.dart'; 17 import 'package:analyzer/src/util/glob.dart';
17 import 'package:path/path.dart' as path; 18 import 'package:path/path.dart' as path;
19 import 'package:watcher/watcher.dart';
18 20
19 /** 21 /**
20 * A function that will return a [UriResolver] that can be used to resolve 22 * A function that will return a [UriResolver] that can be used to resolve
21 * `package:` URIs in [SingleContextManager]. 23 * `package:` URIs in [SingleContextManager].
22 */ 24 */
23 typedef UriResolver PackageResolverProvider(); 25 typedef UriResolver PackageResolverProvider();
24 26
25 /** 27 /**
26 * Implementation of [ContextManager] that supports only one [AnalysisContext]. 28 * Implementation of [ContextManager] that supports only one [AnalysisContext].
27 * So, sources from all analysis roots are added to this single context. All 29 * So, sources from all analysis roots are added to this single context. All
28 * features that could otherwise cause creating additional contexts, such as 30 * features that could otherwise cause creating additional contexts, such as
29 * presence of `pubspec.yaml` or `.packages` files, or `.analysis_options` files 31 * presence of `pubspec.yaml` or `.packages` files, or `.analysis_options` files
30 * are ignored. 32 * are ignored.
31 */ 33 */
32 class SingleContextManager implements ContextManager { 34 class SingleContextManager implements ContextManager {
33 /** 35 /**
34 * The [ResourceProvider] using which paths are converted into [Resource]s. 36 * The [ResourceProvider] using which paths are converted into [Resource]s.
35 */ 37 */
36 final ResourceProvider resourceProvider; 38 final ResourceProvider resourceProvider;
37 39
38 /** 40 /**
41 * The context used to work with file system paths.
42 */
43 path.Context pathContext;
44
45 /**
39 * The manager used to access the SDK that should be associated with a 46 * The manager used to access the SDK that should be associated with a
40 * particular context. 47 * particular context.
41 */ 48 */
42 final DartSdkManager sdkManager; 49 final DartSdkManager sdkManager;
43 50
44 /** 51 /**
45 * A function that will return a [UriResolver] that can be used to resolve 52 * A function that will return a [UriResolver] that can be used to resolve
46 * `package:` URIs. 53 * `package:` URIs.
47 */ 54 */
48 final PackageResolverProvider packageResolverProvider; 55 final PackageResolverProvider packageResolverProvider;
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
82 * The context in which everything is being analyzed. 89 * The context in which everything is being analyzed.
83 */ 90 */
84 AnalysisContext context; 91 AnalysisContext context;
85 92
86 /** 93 /**
87 * The folder associated with the context. 94 * The folder associated with the context.
88 */ 95 */
89 Folder contextFolder; 96 Folder contextFolder;
90 97
91 /** 98 /**
99 * The current [contextFolder] watch subscription.
100 */
101 StreamSubscription<WatchEvent> watchSubscription;
102
103 /**
92 * The [PathFilter] used to filter sources from being analyzed. 104 * The [PathFilter] used to filter sources from being analyzed.
93 */ 105 */
94 PathFilter pathFilter; 106 PathFilter pathFilter;
95 107
96 /** 108 /**
97 * The [packageResolverProvider] must not be `null`. 109 * The [packageResolverProvider] must not be `null`.
98 */ 110 */
99 SingleContextManager(this.resourceProvider, this.sdkManager, 111 SingleContextManager(this.resourceProvider, this.sdkManager,
100 this.packageResolverProvider, this.analyzedFilesGlobs); 112 this.packageResolverProvider, this.analyzedFilesGlobs) {
113 pathContext = resourceProvider.pathContext;
114 }
101 115
102 @override 116 @override
103 Iterable<AnalysisContext> get analysisContexts => 117 Iterable<AnalysisContext> get analysisContexts =>
104 context == null ? <AnalysisContext>[] : <AnalysisContext>[context]; 118 context == null ? <AnalysisContext>[] : <AnalysisContext>[context];
105 119
106 @override 120 @override
107 Map<Folder, AnalysisContext> get folderMap => {contextFolder: context}; 121 Map<Folder, AnalysisContext> get folderMap => {contextFolder: context};
108 122
109 @override 123 @override
110 List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) { 124 List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) {
(...skipping 24 matching lines...) Expand all
135 149
136 @override 150 @override
137 void refresh(List<Resource> roots) { 151 void refresh(List<Resource> roots) {
138 if (context != null) { 152 if (context != null) {
139 // TODO(brianwilkerson) Not sure whether this is right. 153 // TODO(brianwilkerson) Not sure whether this is right.
140 callbacks.removeContext(contextFolder, null); 154 callbacks.removeContext(contextFolder, null);
141 context.dispose(); 155 context.dispose();
142 context = null; 156 context = null;
143 contextFolder = null; 157 contextFolder = null;
144 pathFilter = null; 158 pathFilter = null;
159 if (watchSubscription != null) {
160 watchSubscription.cancel();
161 watchSubscription = null;
162 }
145 setRoots(includedPaths, excludedPaths, packageRoots); 163 setRoots(includedPaths, excludedPaths, packageRoots);
146 } 164 }
147 } 165 }
148 166
149 @override 167 @override
150 void setRoots(List<String> includedPaths, List<String> excludedPaths, 168 void setRoots(List<String> includedPaths, List<String> excludedPaths,
151 Map<String, String> packageRoots) { 169 Map<String, String> packageRoots) {
152 includedPaths = _nonOverlappingPaths(includedPaths); 170 includedPaths = _nonOverlappingPaths(includedPaths);
153 excludedPaths = _nonOverlappingPaths(excludedPaths); 171 excludedPaths = _nonOverlappingPaths(excludedPaths);
154 this.packageRoots = packageRoots; 172 this.packageRoots = packageRoots;
155 _updateNormalizedPackageRoots(); 173 _updateNormalizedPackageRoots();
156 // Update context path. 174 // Update context path.
157 { 175 {
158 String contextPath = _commonPrefix(includedPaths); 176 String contextPath = _commonPrefix(includedPaths);
159 Folder contextFolder = resourceProvider.getFolder(contextPath); 177 Folder contextFolder = resourceProvider.getFolder(contextPath);
160 if (contextFolder != this.contextFolder) { 178 if (contextFolder != this.contextFolder) {
161 if (context != null) { 179 if (context != null) {
162 callbacks.moveContext(this.contextFolder, contextFolder); 180 callbacks.moveContext(this.contextFolder, contextFolder);
163 } 181 }
164 this.contextFolder = contextFolder; 182 this.contextFolder = contextFolder;
165 // TODO(scheglov) watch for changes in `contextFolder` 183 // Start new watcher and cancel the old one.
184 StreamSubscription<WatchEvent> watchSubscription =
185 this.contextFolder.changes.listen(_handleWatchEvent);
186 this.watchSubscription?.cancel();
187 this.watchSubscription = watchSubscription;
166 } 188 }
167 } 189 }
168 if (context == null) { 190 if (context == null) {
169 UriResolver packageResolver = packageResolverProvider(); 191 UriResolver packageResolver = packageResolverProvider();
170 context = callbacks.addContext(contextFolder, new AnalysisOptionsImpl(), 192 context = callbacks.addContext(contextFolder, new AnalysisOptionsImpl(),
171 new CustomPackageResolverDisposition(packageResolver)); 193 new CustomPackageResolverDisposition(packageResolver));
172 ChangeSet changeSet = 194 ChangeSet changeSet =
173 _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths)); 195 _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths));
174 callbacks.applyChangesToContext(contextFolder, changeSet); 196 callbacks.applyChangesToContext(contextFolder, changeSet);
175 } else { 197 } else {
(...skipping 26 matching lines...) Expand all
202 if (_matchesAnyAnalyzedFilesGlob(path) && resource.exists) { 224 if (_matchesAnyAnalyzedFilesGlob(path) && resource.exists) {
203 addedFiles.add(resource); 225 addedFiles.add(resource);
204 } 226 }
205 } else if (resource is Folder) { 227 } else if (resource is Folder) {
206 for (Resource child in _getChildrenSafe(resource)) { 228 for (Resource child in _getChildrenSafe(resource)) {
207 _addFilesInResource(addedFiles, child, excludedPaths); 229 _addFilesInResource(addedFiles, child, excludedPaths);
208 } 230 }
209 } 231 }
210 } 232 }
211 233
234 ChangeSet _buildChangeSet({List<File> added, List<File> removed}) {
235 ChangeSet changeSet = new ChangeSet();
236 if (added != null) {
237 for (File file in added) {
238 Source source = createSourceInContext(context, file);
239 changeSet.addedSource(source);
240 }
241 }
242 if (removed != null) {
243 for (File file in removed) {
244 Source source = createSourceInContext(context, file);
245 changeSet.removedSource(source);
246 }
247 }
248 return changeSet;
249 }
250
251 String _commonPrefix(List<String> paths) {
252 if (paths.isEmpty) {
253 return '';
254 }
255 List<String> left = pathContext.split(paths[0]);
256 int count = left.length;
257 for (int i = 1; i < paths.length; i++) {
258 List<String> right = pathContext.split(paths[i]);
259 count = _commonComponents(left, count, right);
260 }
261 return pathContext.joinAll(left.sublist(0, count));
262 }
263
212 List<Resource> _existingResources(List<String> pathList) { 264 List<Resource> _existingResources(List<String> pathList) {
213 List<Resource> resources = <Resource>[]; 265 List<Resource> resources = <Resource>[];
214 for (String path in pathList) { 266 for (String path in pathList) {
215 Resource resource = resourceProvider.getResource(path); 267 Resource resource = resourceProvider.getResource(path);
216 if (resource is Folder) { 268 if (resource is Folder) {
217 resources.add(resource); 269 resources.add(resource);
218 } else if (!resource.exists) { 270 } else if (!resource.exists) {
219 // Non-existent resources are ignored. TODO(paulberry): we should set 271 // Non-existent resources are ignored. TODO(paulberry): we should set
220 // up a watcher to ensure that if the resource appears later, we will 272 // up a watcher to ensure that if the resource appears later, we will
221 // begin analyzing it. 273 // begin analyzing it.
222 } else if (resource is File) { 274 } else if (resource is File) {
223 resources.add(resource); 275 resources.add(resource);
224 } else { 276 } else {
225 throw new UnimplementedError('$path is not a folder. ' 277 throw new UnimplementedError('$path is not a folder. '
226 'Only support for file and folder analysis is implemented.'); 278 'Only support for file and folder analysis is implemented.');
227 } 279 }
228 } 280 }
229 return resources; 281 return resources;
230 } 282 }
231 283
284 void _handleWatchEvent(WatchEvent event) {
285 String path = event.path;
286 // Ignore if excluded.
287 if (_isExcludedPath(path)) {
288 return;
289 }
290 // Ignore if not in a root.
291 if (!_isContainedIn(includedPaths, path)) {
292 return;
293 }
294 // Handle the change.
295 switch (event.type) {
296 case ChangeType.ADD:
297 Resource resource = resourceProvider.getResource(path);
298 if (resource is File) {
299 if (_matchesAnyAnalyzedFilesGlob(path)) {
300 callbacks.applyChangesToContext(
301 contextFolder, _buildChangeSet(added: <File>[resource]));
302 }
303 }
304 break;
305 case ChangeType.REMOVE:
306 List<Source> sources = context.getSourcesWithFullName(path);
307 if (!sources.isEmpty) {
308 ChangeSet changeSet = new ChangeSet();
309 sources.forEach(changeSet.removedSource);
310 callbacks.applyChangesToContext(contextFolder, changeSet);
311 }
312 break;
313 case ChangeType.MODIFY:
314 List<Source> sources = context.getSourcesWithFullName(path);
315 if (!sources.isEmpty) {
316 ChangeSet changeSet = new ChangeSet();
317 sources.forEach(changeSet.changedSource);
318 callbacks.applyChangesToContext(contextFolder, changeSet);
319 }
320 break;
321 }
322 }
323
232 List<File> _includedFiles( 324 List<File> _includedFiles(
233 List<String> includedPaths, List<String> excludedPaths) { 325 List<String> includedPaths, List<String> excludedPaths) {
234 List<Resource> includedResources = _existingResources(includedPaths); 326 List<Resource> includedResources = _existingResources(includedPaths);
235 List<File> includedFiles = <File>[]; 327 List<File> includedFiles = <File>[];
236 for (Resource resource in includedResources) { 328 for (Resource resource in includedResources) {
237 _addFilesInResource(includedFiles, resource, excludedPaths); 329 _addFilesInResource(includedFiles, resource, excludedPaths);
238 } 330 }
239 return includedFiles; 331 return includedFiles;
240 } 332 }
241 333
334 bool _isContainedIn(List<String> pathList, String path) {
335 for (String pathInList in pathList) {
336 if (_isEqualOrWithin(pathInList, path)) {
337 return true;
338 }
339 }
340 return false;
341 }
342
343 bool _isEqualOrWithin(String parent, String child) {
344 return child == parent || pathContext.isWithin(parent, child);
345 }
346
347 bool _isEqualOrWithinAny(List<String> parents, String child) {
348 for (String parent in parents) {
349 if (_isEqualOrWithin(parent, child)) {
350 return true;
351 }
352 }
353 return false;
354 }
355
356 /**
357 * Return `true` if the given [path] should be excluded, using explicit
358 * or implicit rules.
359 */
360 bool _isExcludedPath(String path) {
361 List<String> parts = resourceProvider.pathContext.split(path);
362 // Implicit rules.
363 for (String part in parts) {
364 if (part.startsWith('.')) {
365 return true;
366 }
367 }
368 // Explicitly excluded paths.
369 if (_isEqualOrWithinAny(excludedPaths, path)) {
370 return true;
371 }
372 // OK
373 return false;
374 }
375
242 /** 376 /**
243 * Return `true` if the given [resource] and children should be excluded 377 * Return `true` if the given [resource] and children should be excluded
244 * because of some implicit exclusion rules, e.g. `.name`. 378 * because of some implicit exclusion rules, e.g. `.name`.
245 */ 379 */
246 bool _isImplicitlyExcludedResource(Resource resource) { 380 bool _isImplicitlyExcludedResource(Resource resource) {
247 String shortName = resource.shortName; 381 String shortName = resource.shortName;
248 if (shortName.startsWith('.')) { 382 if (shortName.startsWith('.')) {
249 return true; 383 return true;
250 } 384 }
251 return false; 385 return false;
252 } 386 }
253 387
254 /** 388 /**
255 * Return `true` if the given [path] matches one of the [analyzedFilesGlobs]. 389 * Return `true` if the given [path] matches one of the [analyzedFilesGlobs].
256 */ 390 */
257 bool _matchesAnyAnalyzedFilesGlob(String path) { 391 bool _matchesAnyAnalyzedFilesGlob(String path) {
258 for (Glob glob in analyzedFilesGlobs) { 392 for (Glob glob in analyzedFilesGlobs) {
259 if (glob.matches(path)) { 393 if (glob.matches(path)) {
260 return true; 394 return true;
261 } 395 }
262 } 396 }
263 return false; 397 return false;
264 } 398 }
265 399
266 /** 400 /**
401 * Return a list consisting of the elements from [pathList] that describe the
402 * minimal set of directories that include everything in the original list of
403 * paths and nothing more. In particular:
404 *
405 * * if a path is in the input list multiple times it will appear at most
406 * once in the output list, and
407 * * if a directory D and a subdirectory of it are both in the input list
408 * then only the directory D will be in the output list.
409 *
410 * The original list is not modified.
411 */
412 List<String> _nonOverlappingPaths(List<String> pathList) {
413 List<String> sortedPaths = new List<String>.from(pathList);
414 sortedPaths.sort((a, b) => a.length - b.length);
415 int pathCount = sortedPaths.length;
416 for (int i = pathCount - 1; i > 0; i--) {
417 String path = sortedPaths[i];
418 for (int j = 0; j < i; j++) {
419 if (_isEqualOrWithin(path, sortedPaths[j])) {
420 sortedPaths.removeAt(i);
421 break;
422 }
423 }
424 }
425 return sortedPaths;
426 }
427
428 /**
267 * Normalize all package root sources by mapping them to folders on the 429 * Normalize all package root sources by mapping them to folders on the
268 * filesystem. Ignore any package root sources that aren't folders. 430 * filesystem. Ignore any package root sources that aren't folders.
269 */ 431 */
270 void _updateNormalizedPackageRoots() { 432 void _updateNormalizedPackageRoots() {
271 normalizedPackageRoots = <String, String>{}; 433 normalizedPackageRoots = <String, String>{};
272 packageRoots.forEach((String sourcePath, String targetPath) { 434 packageRoots.forEach((String sourcePath, String targetPath) {
273 Resource resource = resourceProvider.getResource(sourcePath); 435 Resource resource = resourceProvider.getResource(sourcePath);
274 if (resource is Folder) { 436 if (resource is Folder) {
275 normalizedPackageRoots[resource.path] = targetPath; 437 normalizedPackageRoots[resource.path] = targetPath;
276 } 438 }
277 }); 439 });
278 } 440 }
279 441
280 static ChangeSet _buildChangeSet({List<File> added, List<File> removed}) { 442 /**
281 ChangeSet changeSet = new ChangeSet(); 443 * Create and return a source representing the given [file] within the given
282 if (added != null) { 444 * [context].
283 for (File file in added) { 445 */
284 changeSet.addedSource(file.createSource()); 446 static Source createSourceInContext(AnalysisContext context, File file) {
285 } 447 // TODO(brianwilkerson) Optimize this, by allowing support for source
448 // factories to restore URI's from a file path rather than a source.
449 Source source = file.createSource();
450 if (context == null) {
451 return source;
286 } 452 }
287 if (removed != null) { 453 Uri uri = context.sourceFactory.restoreUri(source);
288 for (File file in removed) { 454 return file.createSource(uri);
289 changeSet.removedSource(file.createSource());
290 }
291 }
292 return changeSet;
293 } 455 }
294 456
295 static int _commonComponents( 457 static int _commonComponents(
296 List<String> left, int count, List<String> right) { 458 List<String> left, int count, List<String> right) {
297 int max = math.min(count, right.length); 459 int max = math.min(count, right.length);
298 for (int i = 0; i < max; i++) { 460 for (int i = 0; i < max; i++) {
299 if (left[i] != right[i]) { 461 if (left[i] != right[i]) {
300 return i; 462 return i;
301 } 463 }
302 } 464 }
303 return max; 465 return max;
304 } 466 }
305 467
306 static String _commonPrefix(List<String> paths) {
307 if (paths.isEmpty) {
308 return '';
309 }
310 List<String> left = path.split(paths[0]);
311 int count = left.length;
312 for (int i = 1; i < paths.length; i++) {
313 List<String> right = path.split(paths[i]);
314 count = _commonComponents(left, count, right);
315 }
316 return path.joinAll(left.sublist(0, count));
317 }
318
319 /** 468 /**
320 * Return a list of all the files in the [left] that are not in the [right]. 469 * Return a list of all the files in the [left] that are not in the [right].
321 */ 470 */
322 static List<File> _diff(List<File> left, List<File> right) { 471 static List<File> _diff(List<File> left, List<File> right) {
323 List<File> diff = new List.from(left); 472 List<File> diff = new List.from(left);
324 for (File file in right) { 473 for (File file in right) {
325 diff.remove(file); 474 diff.remove(file);
326 } 475 }
327 return diff; 476 return diff;
328 } 477 }
329 478
330 static List<Resource> _getChildrenSafe(Folder folder) { 479 static List<Resource> _getChildrenSafe(Folder folder) {
331 try { 480 try {
332 return folder.getChildren(); 481 return folder.getChildren();
333 } on FileSystemException { 482 } on FileSystemException {
334 // The folder either doesn't exist or cannot be read. 483 // The folder either doesn't exist or cannot be read.
335 // Either way, there are no children. 484 // Either way, there are no children.
336 return const <Resource>[]; 485 return const <Resource>[];
337 } 486 }
338 } 487 }
339
340 static bool _isContainedIn(List<String> pathList, String path) {
341 for (String pathInList in pathList) {
342 if (_isEqualOrWithin(path, pathInList)) {
343 return true;
344 }
345 }
346 return false;
347 }
348
349 static bool _isEqualOrWithin(String parent, String child) {
350 return child == parent || path.isWithin(parent, child);
351 }
352
353 static bool _isEqualOrWithinAny(List<String> parents, String child) {
354 for (String parent in parents) {
355 if (_isEqualOrWithin(parent, child)) {
356 return true;
357 }
358 }
359 return false;
360 }
361
362 /**
363 * Return a list consisting of the elements from [pathList] that describe the
364 * minimal set of directories that include everything in the original list of
365 * paths and nothing more. In particular:
366 *
367 * * if a path is in the input list multiple times it will appear at most
368 * once in the output list, and
369 * * if a directory D and a subdirectory of it are both in the input list
370 * then only the directory D will be in the output list.
371 *
372 * The original list is not modified.
373 */
374 static List<String> _nonOverlappingPaths(List<String> pathList) {
375 List<String> sortedPaths = new List<String>.from(pathList);
376 sortedPaths.sort((a, b) => a.length - b.length);
377 int pathCount = sortedPaths.length;
378 for (int i = pathCount - 1; i > 0; i--) {
379 String path = sortedPaths[i];
380 for (int j = 0; j < i; j++) {
381 if (_isEqualOrWithin(path, sortedPaths[j])) {
382 sortedPaths.removeAt(i);
383 break;
384 }
385 }
386 }
387 return sortedPaths;
388 }
389 } 488 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/test/single_context_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698