| Index: pkg/analysis_server/lib/src/analysis_server.dart
|
| diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
|
| index f256834850bc5edc557f0a4ebbe7387d8c4a5d41..145c000d09a215624e9d7fcf452aafccd4a04233 100644
|
| --- a/pkg/analysis_server/lib/src/analysis_server.dart
|
| +++ b/pkg/analysis_server/lib/src/analysis_server.dart
|
| @@ -7,137 +7,28 @@ library analysis.server;
|
| import 'dart:async';
|
| import 'dart:collection';
|
|
|
| -import 'package:analyzer/file_system/file_system.dart';
|
| import 'package:analysis_server/src/analysis_logger.dart';
|
| import 'package:analysis_server/src/channel/channel.dart';
|
| import 'package:analysis_server/src/context_manager.dart';
|
| -import 'package:analysis_server/src/operation/operation_analysis.dart';
|
| import 'package:analysis_server/src/operation/operation.dart';
|
| +import 'package:analysis_server/src/operation/operation_analysis.dart';
|
| import 'package:analysis_server/src/operation/operation_queue.dart';
|
| import 'package:analysis_server/src/protocol.dart' hide Element;
|
| +import 'package:analysis_server/src/services/correction/namespace.dart';
|
| +import 'package:analysis_server/src/services/index/index.dart';
|
| +import 'package:analysis_server/src/services/search/search_engine.dart';
|
| +import 'package:analyzer/file_system/file_system.dart';
|
| import 'package:analyzer/source/package_map_provider.dart';
|
| import 'package:analyzer/src/generated/ast.dart';
|
| +import 'package:analyzer/src/generated/element.dart';
|
| import 'package:analyzer/src/generated/engine.dart';
|
| -import 'package:analyzer/src/generated/source.dart';
|
| +import 'package:analyzer/src/generated/java_engine.dart';
|
| import 'package:analyzer/src/generated/sdk.dart';
|
| +import 'package:analyzer/src/generated/source.dart';
|
| import 'package:analyzer/src/generated/source_io.dart';
|
| -import 'package:analyzer/src/generated/java_engine.dart';
|
| -import 'package:analysis_server/src/services/index/index.dart';
|
| -import 'package:analysis_server/src/services/search/search_engine.dart';
|
| -import 'package:analyzer/src/generated/element.dart';
|
| -import 'package:analysis_server/src/services/correction/namespace.dart';
|
| -
|
| -
|
| -class ServerContextManager extends ContextManager {
|
| - final AnalysisServer analysisServer;
|
| -
|
| - /**
|
| - * The default options used to create new analysis contexts.
|
| - */
|
| - AnalysisOptionsImpl defaultOptions = new AnalysisOptionsImpl();
|
| -
|
| - /**
|
| - * The controller for sending [ContextsChangedEvent]s.
|
| - */
|
| - StreamController<ContextsChangedEvent> _onContextsChangedController;
|
| -
|
| - ServerContextManager(this.analysisServer, ResourceProvider resourceProvider,
|
| - PackageMapProvider packageMapProvider)
|
| - : super(resourceProvider, packageMapProvider) {
|
| - _onContextsChangedController = new StreamController<ContextsChangedEvent>();
|
| - }
|
| -
|
| - /**
|
| - * The stream that is notified when contexts are added or removed.
|
| - */
|
| - Stream<ContextsChangedEvent> get onContextsChanged =>
|
| - _onContextsChangedController.stream;
|
| -
|
| - @override
|
| - void addContext(Folder folder, UriResolver packageUriResolver) {
|
| - AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
|
| - analysisServer.folderMap[folder] = context;
|
| - context.sourceFactory = _createSourceFactory(packageUriResolver);
|
| - context.analysisOptions = new AnalysisOptionsImpl.con1(defaultOptions);
|
| - _onContextsChangedController.add(
|
| - new ContextsChangedEvent(added: [context]));
|
| - analysisServer.schedulePerformAnalysisOperation(context);
|
| - }
|
| -
|
| - @override
|
| - void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) {
|
| - AnalysisContext context = analysisServer.folderMap[contextFolder];
|
| - if (context != null) {
|
| - context.applyChanges(changeSet);
|
| - analysisServer.schedulePerformAnalysisOperation(context);
|
| - }
|
| - }
|
| -
|
| - @override
|
| - void removeContext(Folder folder) {
|
| - AnalysisContext context = analysisServer.folderMap.remove(folder);
|
| - if (analysisServer.index != null) {
|
| - analysisServer.index.removeContext(context);
|
| - }
|
| - _onContextsChangedController.add(
|
| - new ContextsChangedEvent(removed: [context]));
|
| - analysisServer.sendContextAnalysisDoneNotifications(
|
| - context,
|
| - AnalysisDoneReason.CONTEXT_REMOVED);
|
| - }
|
| -
|
| - @override
|
| - void updateContextPackageUriResolver(Folder contextFolder,
|
| - UriResolver packageUriResolver) {
|
| - AnalysisContext context = analysisServer.folderMap[contextFolder];
|
| - context.sourceFactory = _createSourceFactory(packageUriResolver);
|
| - _onContextsChangedController.add(
|
| - new ContextsChangedEvent(changed: [context]));
|
| - analysisServer.schedulePerformAnalysisOperation(context);
|
| - }
|
| -
|
| - /**
|
| - * Set up a [SourceFactory] that resolves packages using the given
|
| - * [packageUriResolver].
|
| - */
|
| - SourceFactory _createSourceFactory(UriResolver packageUriResolver) {
|
| - List<UriResolver> resolvers = <UriResolver>[
|
| - new DartUriResolver(analysisServer.defaultSdk),
|
| - new ResourceUriResolver(resourceProvider),
|
| - packageUriResolver];
|
| - return new SourceFactory(resolvers);
|
| - }
|
| -}
|
| -
|
| -
|
| -/**
|
| - * A [ContextsChangedEvent] indicate what contexts were added or removed.
|
| - *
|
| - * No context should be added to the event more than once. It does not make
|
| - * sense, for example, for a context to be both added and removed.
|
| - */
|
| -class ContextsChangedEvent {
|
| -
|
| - /**
|
| - * The contexts that were added to the server.
|
| - */
|
| - final List<AnalysisContext> added;
|
|
|
| - /**
|
| - * The contexts that were changed.
|
| - */
|
| - final List<AnalysisContext> changed;
|
| -
|
| - /**
|
| - * The contexts that were removed from the server.
|
| - */
|
| - final List<AnalysisContext> removed;
|
|
|
| - ContextsChangedEvent({
|
| - this.added: AnalysisContext.EMPTY_LIST,
|
| - this.changed: AnalysisContext.EMPTY_LIST,
|
| - this.removed: AnalysisContext.EMPTY_LIST});
|
| -}
|
| +typedef void OptionUpdater(AnalysisOptionsImpl options);
|
|
|
|
|
| /**
|
| @@ -311,107 +202,9 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| - * If the given notice applies to a file contained within an analysis root,
|
| - * notify interested parties that the file has been (at least partially)
|
| - * analyzed.
|
| - */
|
| - void fileAnalyzed(ChangeNotice notice) {
|
| - if (contextDirectoryManager.isInAnalysisRoot(notice.source.fullName)) {
|
| - _onFileAnalyzedController.add(notice);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Schedules execution of the given [ServerOperation].
|
| - */
|
| - void scheduleOperation(ServerOperation operation) {
|
| - addOperation(operation);
|
| - if (!performOperationPending) {
|
| - _schedulePerformOperation();
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Schedules analysis of the given context.
|
| - */
|
| - void schedulePerformAnalysisOperation(AnalysisContext context) {
|
| - _onAnalysisStartedController.add(context);
|
| - scheduleOperation(new PerformAnalysisOperation(context, false));
|
| - }
|
| -
|
| - /**
|
| - * Send the given [notification] to the client.
|
| - */
|
| - void sendNotification(Notification notification) {
|
| - channel.sendNotification(notification);
|
| - }
|
| -
|
| - /**
|
| - * Send the given [response] to the client.
|
| - */
|
| - void sendResponse(Response response) {
|
| - channel.sendResponse(response);
|
| - }
|
| -
|
| - /**
|
| - * Set the priority files to the given [files].
|
| - */
|
| - void setPriorityFiles(Request request, List<String> files) {
|
| - Map<AnalysisContext, List<Source>> sourceMap =
|
| - new HashMap<AnalysisContext, List<Source>>();
|
| - List<String> unanalyzed = new List<String>();
|
| - files.forEach((file) {
|
| - AnalysisContext analysisContext = getAnalysisContext(file);
|
| - if (analysisContext == null) {
|
| - unanalyzed.add(file);
|
| - } else {
|
| - List<Source> sourceList = sourceMap[analysisContext];
|
| - if (sourceList == null) {
|
| - sourceList = <Source>[];
|
| - sourceMap[analysisContext] = sourceList;
|
| - }
|
| - sourceList.add(getSource(file));
|
| - }
|
| - });
|
| - if (unanalyzed.isNotEmpty) {
|
| - StringBuffer buffer = new StringBuffer();
|
| - buffer.writeAll(unanalyzed, ', ');
|
| - throw new RequestFailure(
|
| - new Response.unanalyzedPriorityFiles(request, buffer.toString()));
|
| - }
|
| - folderMap.forEach((Folder folder, AnalysisContext context) {
|
| - List<Source> sourceList = sourceMap[context];
|
| - if (sourceList == null) {
|
| - sourceList = Source.EMPTY_ARRAY;
|
| - }
|
| - context.analysisPriorityOrder = sourceList;
|
| - });
|
| - }
|
| -
|
| - /**
|
| - * Use the given updaters to update the values of the options in every
|
| - * existing analysis context.
|
| + * The stream that is notified when analysis is complete.
|
| */
|
| - void updateOptions(List<OptionUpdater> optionUpdaters) {
|
| - //
|
| - // Update existing contexts.
|
| - //
|
| - folderMap.forEach((Folder folder, AnalysisContext context) {
|
| - AnalysisOptionsImpl options =
|
| - new AnalysisOptionsImpl.con1(context.analysisOptions);
|
| - optionUpdaters.forEach((OptionUpdater optionUpdater) {
|
| - optionUpdater(options);
|
| - });
|
| - context.analysisOptions = options;
|
| - });
|
| - //
|
| - // Update the defaults used to create new contexts.
|
| - //
|
| - AnalysisOptionsImpl options = contextDirectoryManager.defaultOptions;
|
| - optionUpdaters.forEach((OptionUpdater optionUpdater) {
|
| - optionUpdater(options);
|
| - });
|
| - }
|
| + Stream get onAnalysisComplete => _onAnalysisCompleteController.stream;
|
|
|
| /**
|
| * The stream that is notified when analysis of a context is started.
|
| @@ -421,11 +214,6 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| - * The stream that is notified when analysis is complete.
|
| - */
|
| - Stream get onAnalysisComplete => _onAnalysisCompleteController.stream;
|
| -
|
| - /**
|
| * The stream that is notified when a single file has been analyzed.
|
| */
|
| Stream get onFileAnalyzed => _onFileAnalyzedController.stream;
|
| @@ -454,31 +242,222 @@ class AnalysisServer {
|
| running = false;
|
| }
|
|
|
| -// TODO(brianwilkerson) Add the following method after 'prioritySources' has
|
| -// been added to InternalAnalysisContext.
|
| -// /**
|
| -// * Return a list containing the full names of all of the sources that are
|
| -// * priority sources.
|
| -// */
|
| -// List<String> getPriorityFiles() {
|
| -// List<String> priorityFiles = new List<String>();
|
| -// folderMap.values.forEach((ContextDirectory directory) {
|
| -// InternalAnalysisContext context = directory.context;
|
| -// context.prioritySources.forEach((Source source) {
|
| -// priorityFiles.add(source.fullName);
|
| -// });
|
| -// });
|
| -// return priorityFiles;
|
| -// }
|
| -
|
| /**
|
| - * Handle a [request] that was read from the communication channel.
|
| + * If the given notice applies to a file contained within an analysis root,
|
| + * notify interested parties that the file has been (at least partially)
|
| + * analyzed.
|
| */
|
| - void handleRequest(Request request) {
|
| - runZoned(() {
|
| - int count = handlers.length;
|
| - for (int i = 0; i < count; i++) {
|
| - try {
|
| + void fileAnalyzed(ChangeNotice notice) {
|
| + if (contextDirectoryManager.isInAnalysisRoot(notice.source.fullName)) {
|
| + _onFileAnalyzedController.add(notice);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Return the [AnalysisContext] that is used to analyze the given [path].
|
| + * Return `null` if there is no such context.
|
| + */
|
| + AnalysisContext getAnalysisContext(String path) {
|
| + // try to find a containing context
|
| + for (Folder folder in folderMap.keys) {
|
| + if (folder.contains(path)) {
|
| + return folderMap[folder];
|
| + }
|
| + }
|
| + // check if there is a context that analyzed this source
|
| + Source source = getSource(path);
|
| + for (AnalysisContext context in folderMap.values) {
|
| + SourceKind kind = context.getKindOf(source);
|
| + if (kind != null) {
|
| + return context;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Return the [AnalysisContext]s that are being used to analyze the analysis
|
| + * roots.
|
| + */
|
| + Iterable<AnalysisContext> getAnalysisContexts() {
|
| + return folderMap.values;
|
| + }
|
| +
|
| + /**
|
| + * Returns [Element]s at the given [offset] of the given [file].
|
| + *
|
| + * May be empty if cannot be resolved, but not `null`.
|
| + */
|
| + List<Element> getElementsAtOffset(String file, int offset) {
|
| + List<AstNode> nodes = getNodesAtOffset(file, offset);
|
| + return getElementsOfNodes(nodes, offset);
|
| + }
|
| +
|
| + /**
|
| + * Returns [Element]s of the given [nodes].
|
| + *
|
| + * May be empty if not resolved, but not `null`.
|
| + */
|
| + List<Element> getElementsOfNodes(List<AstNode> nodes, int offset) {
|
| + List<Element> elements = <Element>[];
|
| + for (AstNode node in nodes) {
|
| + if (node is SimpleIdentifier && node.parent is LibraryIdentifier) {
|
| + node = node.parent;
|
| + }
|
| + if (node is LibraryIdentifier) {
|
| + node = node.parent;
|
| + }
|
| + Element element = ElementLocator.locateWithOffset(node, offset);
|
| + if (node is SimpleIdentifier && element is PrefixElement) {
|
| + element = getImportElement(node);
|
| + }
|
| + if (element != null) {
|
| + elements.add(element);
|
| + }
|
| + }
|
| + return elements;
|
| + }
|
| +
|
| + /**
|
| + * Return an analysis error info containing the array of all of the errors and
|
| + * the line info associated with [file].
|
| + *
|
| + * Returns `null` if [file] does not belong to any [AnalysisContext], or the
|
| + * file does not exist.
|
| + *
|
| + * The array of errors will be empty if there are no errors in [file]. The
|
| + * errors contained in the array can be incomplete.
|
| + *
|
| + * This method does not wait for all errors to be computed, and returns just
|
| + * the current state.
|
| + */
|
| + AnalysisErrorInfo getErrors(String file) {
|
| + // prepare AnalysisContext
|
| + AnalysisContext context = getAnalysisContext(file);
|
| + if (context == null) {
|
| + return null;
|
| + }
|
| + // prepare Source
|
| + Source source = getSource(file);
|
| + if (context.getKindOf(source) == SourceKind.UNKNOWN) {
|
| + return null;
|
| + }
|
| + // get errors for the file
|
| + return context.getErrors(source);
|
| + }
|
| +
|
| + /**
|
| + * Returns resolved [AstNode]s at the given [offset] of the given [file].
|
| + *
|
| + * May be empty, but not `null`.
|
| + */
|
| + List<AstNode> getNodesAtOffset(String file, int offset) {
|
| + List<CompilationUnit> units = getResolvedCompilationUnits(file);
|
| + List<AstNode> nodes = <AstNode>[];
|
| + for (CompilationUnit unit in units) {
|
| + AstNode node = new NodeLocator.con1(offset).searchWithin(unit);
|
| + if (node != null) {
|
| + nodes.add(node);
|
| + }
|
| + }
|
| + return nodes;
|
| + }
|
| +
|
| +// TODO(brianwilkerson) Add the following method after 'prioritySources' has
|
| +// been added to InternalAnalysisContext.
|
| +// /**
|
| +// * Return a list containing the full names of all of the sources that are
|
| +// * priority sources.
|
| +// */
|
| +// List<String> getPriorityFiles() {
|
| +// List<String> priorityFiles = new List<String>();
|
| +// folderMap.values.forEach((ContextDirectory directory) {
|
| +// InternalAnalysisContext context = directory.context;
|
| +// context.prioritySources.forEach((Source source) {
|
| +// priorityFiles.add(source.fullName);
|
| +// });
|
| +// });
|
| +// return priorityFiles;
|
| +// }
|
| +
|
| + /**
|
| + * Returns resolved [CompilationUnit]s of the Dart file with the given [path].
|
| + *
|
| + * May be empty, but not `null`.
|
| + */
|
| + List<CompilationUnit> getResolvedCompilationUnits(String path) {
|
| + List<CompilationUnit> units = <CompilationUnit>[];
|
| + // prepare AnalysisContext
|
| + AnalysisContext context = getAnalysisContext(path);
|
| + if (context == null) {
|
| + return units;
|
| + }
|
| + // add a unit for each unit/library combination
|
| + Source unitSource = getSource(path);
|
| + List<Source> librarySources = context.getLibrariesContaining(unitSource);
|
| + for (Source librarySource in librarySources) {
|
| + CompilationUnit unit =
|
| + context.resolveCompilationUnit2(unitSource, librarySource);
|
| + if (unit != null) {
|
| + units.add(unit);
|
| + }
|
| + }
|
| + // done
|
| + return units;
|
| + }
|
| +
|
| + /**
|
| + * Returns the [CompilationUnit] of the Dart file with the given [path] that
|
| + * should be used to resend notifications for already resolved unit.
|
| + * Returns `null` if the file is not a part of any context, library has not
|
| + * been yet resolved, or any problem happened.
|
| + */
|
| + CompilationUnit getResolvedCompilationUnitToResendNotification(String path) {
|
| + // prepare AnalysisContext
|
| + AnalysisContext context = getAnalysisContext(path);
|
| + if (context == null) {
|
| + return null;
|
| + }
|
| + // prepare sources
|
| + Source unitSource = getSource(path);
|
| + List<Source> librarySources = context.getLibrariesContaining(unitSource);
|
| + if (librarySources.isEmpty) {
|
| + return null;
|
| + }
|
| + // if library has not been resolved yet, the unit will be resolved later
|
| + Source librarySource = librarySources[0];
|
| + if (context.getLibraryElement(librarySource) == null) {
|
| + return null;
|
| + }
|
| + // if library has been already resolved, resolve unit
|
| + return context.resolveCompilationUnit2(unitSource, librarySource);
|
| + }
|
| +
|
| + /**
|
| + * Return the [Source] of the Dart file with the given [path].
|
| + */
|
| + Source getSource(String path) {
|
| + // try SDK
|
| + {
|
| + Uri uri = resourceProvider.pathContext.toUri(path);
|
| + Source sdkSource = defaultSdk.fromFileUri(uri);
|
| + if (sdkSource != null) {
|
| + return sdkSource;
|
| + }
|
| + }
|
| + // file-based source
|
| + File file = resourceProvider.getResource(path);
|
| + return file.createSource();
|
| + }
|
| +
|
| + /**
|
| + * Handle a [request] that was read from the communication channel.
|
| + */
|
| + void handleRequest(Request request) {
|
| + runZoned(() {
|
| + int count = handlers.length;
|
| + for (int i = 0; i < count; i++) {
|
| + try {
|
| Response response = handlers[i].handleRequest(request);
|
| if (response == Response.DELAYED_RESPONSE) {
|
| return;
|
| @@ -514,15 +493,6 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| - * Returns `true` if errors should be reported for [file] with the given
|
| - * absolute path.
|
| - */
|
| - bool shouldSendErrorsNotificationFor(String file) {
|
| - // TODO(scheglov) add support for the "--no-error-notification" flag.
|
| - return contextDirectoryManager.isInAnalysisRoot(file);
|
| - }
|
| -
|
| - /**
|
| * Return `true` if analysis is complete.
|
| */
|
| bool isAnalysisComplete() {
|
| @@ -538,6 +508,39 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| + * Returns a [Future] completing when [file] has been completely analyzed, in
|
| + * particular, all its errors have been computed. The future is completed
|
| + * with an [AnalysisDoneReason] indicating what caused the file's analysis to
|
| + * be considered complete.
|
| + *
|
| + * If the given file doesn't belong to any context, null is returned.
|
| + *
|
| + * TODO(scheglov) this method should be improved.
|
| + *
|
| + * 1. The analysis context should be told to analyze this particular file ASAP.
|
| + *
|
| + * 2. We should complete the future as soon as the file is analyzed (not wait
|
| + * until the context is completely finished)
|
| + */
|
| + Future<AnalysisDoneReason> onFileAnalysisComplete(String file) {
|
| + // prepare AnalysisContext
|
| + AnalysisContext context = getAnalysisContext(file);
|
| + if (context == null) {
|
| + return null;
|
| + }
|
| + // schedule context analysis
|
| + schedulePerformAnalysisOperation(context);
|
| + // associate with the context completer
|
| + Completer<AnalysisDoneReason> completer =
|
| + contextAnalysisDoneCompleters[context];
|
| + if (completer == null) {
|
| + completer = new Completer<AnalysisDoneReason>();
|
| + contextAnalysisDoneCompleters[context] = completer;
|
| + }
|
| + return completer.future;
|
| + }
|
| +
|
| + /**
|
| * Perform the next available [ServerOperation].
|
| */
|
| void performOperation() {
|
| @@ -593,8 +596,53 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| - * Send status notification to the client. The `operation` is the operation
|
| - * being performed or `null` if analysis is complete.
|
| + * Schedules execution of the given [ServerOperation].
|
| + */
|
| + void scheduleOperation(ServerOperation operation) {
|
| + addOperation(operation);
|
| + if (!performOperationPending) {
|
| + _schedulePerformOperation();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Schedules analysis of the given context.
|
| + */
|
| + void schedulePerformAnalysisOperation(AnalysisContext context) {
|
| + _onAnalysisStartedController.add(context);
|
| + scheduleOperation(new PerformAnalysisOperation(context, false));
|
| + }
|
| +
|
| + /**
|
| + * This method is called when analysis of the given [AnalysisContext] is
|
| + * done.
|
| + */
|
| + void sendContextAnalysisDoneNotifications(AnalysisContext context,
|
| + AnalysisDoneReason reason) {
|
| + Completer<AnalysisDoneReason> completer =
|
| + contextAnalysisDoneCompleters.remove(context);
|
| + if (completer != null) {
|
| + completer.complete(reason);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Send the given [notification] to the client.
|
| + */
|
| + void sendNotification(Notification notification) {
|
| + channel.sendNotification(notification);
|
| + }
|
| +
|
| + /**
|
| + * Send the given [response] to the client.
|
| + */
|
| + void sendResponse(Response response) {
|
| + channel.sendResponse(response);
|
| + }
|
| +
|
| + /**
|
| + * Send status notification to the client. The `operation` is the operation
|
| + * being performed or `null` if analysis is complete.
|
| */
|
| void sendStatusNotification(ServerOperation operation) {
|
| // Only send status when subscribed.
|
| @@ -638,51 +686,6 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| - * Implementation for `analysis.updateContent`.
|
| - */
|
| - void updateContent(String id, Map<String, dynamic> changes) {
|
| - changes.forEach((file, change) {
|
| - AnalysisContext analysisContext = getAnalysisContext(file);
|
| - // TODO(paulberry): handle the case where a file is referred to by more
|
| - // than one context (e.g package A depends on package B using a local
|
| - // path, user has both packages open for editing in separate contexts,
|
| - // and user modifies a file in package B).
|
| - if (analysisContext != null) {
|
| - Source source = getSource(file);
|
| - if (change is AddContentOverlay) {
|
| - analysisContext.setContents(source, change.content);
|
| - } else if (change is ChangeContentOverlay) {
|
| - // TODO(paulberry): an error should be generated if source is not
|
| - // currently in the content cache.
|
| - TimestampedData<String> oldContents =
|
| - analysisContext.getContents(source);
|
| - String newContents;
|
| - try {
|
| - newContents =
|
| - SourceEdit.applySequence(oldContents.data, change.edits);
|
| - } on RangeError {
|
| - throw new RequestFailure(
|
| - new Response(
|
| - id,
|
| - error: new RequestError(
|
| - RequestErrorCode.INVALID_OVERLAY_CHANGE,
|
| - 'Invalid overlay change')));
|
| - }
|
| - // TODO(paulberry): to aid in incremental processing it would be
|
| - // better to use setChangedContents.
|
| - analysisContext.setContents(source, newContents);
|
| - } else if (change is RemoveContentOverlay) {
|
| - analysisContext.setContents(source, null);
|
| - } else {
|
| - // Protocol parsing should have ensured that we never get here.
|
| - throw new AnalysisException('Illegal change type');
|
| - }
|
| - schedulePerformAnalysisOperation(analysisContext);
|
| - }
|
| - });
|
| - }
|
| -
|
| - /**
|
| * Implementation for `analysis.setSubscriptions`.
|
| */
|
| void setAnalysisSubscriptions(Map<AnalysisService,
|
| @@ -736,239 +739,127 @@ class AnalysisServer {
|
| }
|
|
|
| /**
|
| - * Return the [AnalysisContext]s that are being used to analyze the analysis
|
| - * roots.
|
| - */
|
| - Iterable<AnalysisContext> getAnalysisContexts() {
|
| - return folderMap.values;
|
| - }
|
| -
|
| - /**
|
| - * Return the [AnalysisContext] that is used to analyze the given [path].
|
| - * Return `null` if there is no such context.
|
| + * Set the priority files to the given [files].
|
| */
|
| - AnalysisContext getAnalysisContext(String path) {
|
| - // try to find a containing context
|
| - for (Folder folder in folderMap.keys) {
|
| - if (folder.contains(path)) {
|
| - return folderMap[folder];
|
| - }
|
| - }
|
| - // check if there is a context that analyzed this source
|
| - Source source = getSource(path);
|
| - for (AnalysisContext context in folderMap.values) {
|
| - SourceKind kind = context.getKindOf(source);
|
| - if (kind != null) {
|
| - return context;
|
| + void setPriorityFiles(Request request, List<String> files) {
|
| + Map<AnalysisContext, List<Source>> sourceMap =
|
| + new HashMap<AnalysisContext, List<Source>>();
|
| + List<String> unanalyzed = new List<String>();
|
| + files.forEach((file) {
|
| + AnalysisContext analysisContext = getAnalysisContext(file);
|
| + if (analysisContext == null) {
|
| + unanalyzed.add(file);
|
| + } else {
|
| + List<Source> sourceList = sourceMap[analysisContext];
|
| + if (sourceList == null) {
|
| + sourceList = <Source>[];
|
| + sourceMap[analysisContext] = sourceList;
|
| + }
|
| + sourceList.add(getSource(file));
|
| }
|
| + });
|
| + if (unanalyzed.isNotEmpty) {
|
| + StringBuffer buffer = new StringBuffer();
|
| + buffer.writeAll(unanalyzed, ', ');
|
| + throw new RequestFailure(
|
| + new Response.unanalyzedPriorityFiles(request, buffer.toString()));
|
| }
|
| - return null;
|
| - }
|
| -
|
| - /**
|
| - * Return the [Source] of the Dart file with the given [path].
|
| - */
|
| - Source getSource(String path) {
|
| - // try SDK
|
| - {
|
| - Uri uri = resourceProvider.pathContext.toUri(path);
|
| - Source sdkSource = defaultSdk.fromFileUri(uri);
|
| - if (sdkSource != null) {
|
| - return sdkSource;
|
| + folderMap.forEach((Folder folder, AnalysisContext context) {
|
| + List<Source> sourceList = sourceMap[context];
|
| + if (sourceList == null) {
|
| + sourceList = Source.EMPTY_ARRAY;
|
| }
|
| - }
|
| - // file-based source
|
| - File file = resourceProvider.getResource(path);
|
| - return file.createSource();
|
| - }
|
| -
|
| - /**
|
| - * Returns the [CompilationUnit] of the Dart file with the given [path] that
|
| - * should be used to resend notifications for already resolved unit.
|
| - * Returns `null` if the file is not a part of any context, library has not
|
| - * been yet resolved, or any problem happened.
|
| - */
|
| - CompilationUnit getResolvedCompilationUnitToResendNotification(String path) {
|
| - // prepare AnalysisContext
|
| - AnalysisContext context = getAnalysisContext(path);
|
| - if (context == null) {
|
| - return null;
|
| - }
|
| - // prepare sources
|
| - Source unitSource = getSource(path);
|
| - List<Source> librarySources = context.getLibrariesContaining(unitSource);
|
| - if (librarySources.isEmpty) {
|
| - return null;
|
| - }
|
| - // if library has not been resolved yet, the unit will be resolved later
|
| - Source librarySource = librarySources[0];
|
| - if (context.getLibraryElement(librarySource) == null) {
|
| - return null;
|
| - }
|
| - // if library has been already resolved, resolve unit
|
| - return context.resolveCompilationUnit2(unitSource, librarySource);
|
| - }
|
| -
|
| - /**
|
| - * Return an analysis error info containing the array of all of the errors and
|
| - * the line info associated with [file].
|
| - *
|
| - * Returns `null` if [file] does not belong to any [AnalysisContext], or the
|
| - * file does not exist.
|
| - *
|
| - * The array of errors will be empty if there are no errors in [file]. The
|
| - * errors contained in the array can be incomplete.
|
| - *
|
| - * This method does not wait for all errors to be computed, and returns just
|
| - * the current state.
|
| - */
|
| - AnalysisErrorInfo getErrors(String file) {
|
| - // prepare AnalysisContext
|
| - AnalysisContext context = getAnalysisContext(file);
|
| - if (context == null) {
|
| - return null;
|
| - }
|
| - // prepare Source
|
| - Source source = getSource(file);
|
| - if (context.getKindOf(source) == SourceKind.UNKNOWN) {
|
| - return null;
|
| - }
|
| - // get errors for the file
|
| - return context.getErrors(source);
|
| + context.analysisPriorityOrder = sourceList;
|
| + });
|
| }
|
|
|
| /**
|
| - * Returns resolved [CompilationUnit]s of the Dart file with the given [path].
|
| - *
|
| - * May be empty, but not `null`.
|
| + * Returns `true` if errors should be reported for [file] with the given
|
| + * absolute path.
|
| */
|
| - List<CompilationUnit> getResolvedCompilationUnits(String path) {
|
| - List<CompilationUnit> units = <CompilationUnit>[];
|
| - // prepare AnalysisContext
|
| - AnalysisContext context = getAnalysisContext(path);
|
| - if (context == null) {
|
| - return units;
|
| - }
|
| - // add a unit for each unit/library combination
|
| - Source unitSource = getSource(path);
|
| - List<Source> librarySources = context.getLibrariesContaining(unitSource);
|
| - for (Source librarySource in librarySources) {
|
| - CompilationUnit unit =
|
| - context.resolveCompilationUnit2(unitSource, librarySource);
|
| - if (unit != null) {
|
| - units.add(unit);
|
| - }
|
| - }
|
| - // done
|
| - return units;
|
| + bool shouldSendErrorsNotificationFor(String file) {
|
| + // TODO(scheglov) add support for the "--no-error-notification" flag.
|
| + return contextDirectoryManager.isInAnalysisRoot(file);
|
| }
|
|
|
| - /**
|
| - * Returns resolved [AstNode]s at the given [offset] of the given [file].
|
| - *
|
| - * May be empty, but not `null`.
|
| - */
|
| - List<AstNode> getNodesAtOffset(String file, int offset) {
|
| - List<CompilationUnit> units = getResolvedCompilationUnits(file);
|
| - List<AstNode> nodes = <AstNode>[];
|
| - for (CompilationUnit unit in units) {
|
| - AstNode node = new NodeLocator.con1(offset).searchWithin(unit);
|
| - if (node != null) {
|
| - nodes.add(node);
|
| - }
|
| + void shutdown() {
|
| + running = false;
|
| + if (index != null) {
|
| + index.clear();
|
| + index.stop();
|
| }
|
| - return nodes;
|
| - }
|
| -
|
| - /**
|
| - * Returns [Element]s at the given [offset] of the given [file].
|
| - *
|
| - * May be empty if cannot be resolved, but not `null`.
|
| - */
|
| - List<Element> getElementsAtOffset(String file, int offset) {
|
| - List<AstNode> nodes = getNodesAtOffset(file, offset);
|
| - return getElementsOfNodes(nodes, offset);
|
| + // Defer closing the channel so that the shutdown response can be sent.
|
| + new Future(channel.close);
|
| }
|
|
|
| /**
|
| - * Returns [Element]s of the given [nodes].
|
| - *
|
| - * May be empty if not resolved, but not `null`.
|
| + * Implementation for `analysis.updateContent`.
|
| */
|
| - List<Element> getElementsOfNodes(List<AstNode> nodes, int offset) {
|
| - List<Element> elements = <Element>[];
|
| - for (AstNode node in nodes) {
|
| - if (node is SimpleIdentifier && node.parent is LibraryIdentifier) {
|
| - node = node.parent;
|
| - }
|
| - if (node is LibraryIdentifier) {
|
| - node = node.parent;
|
| - }
|
| - Element element = ElementLocator.locateWithOffset(node, offset);
|
| - if (node is SimpleIdentifier && element is PrefixElement) {
|
| - element = getImportElement(node);
|
| - }
|
| - if (element != null) {
|
| - elements.add(element);
|
| + void updateContent(String id, Map<String, dynamic> changes) {
|
| + changes.forEach((file, change) {
|
| + AnalysisContext analysisContext = getAnalysisContext(file);
|
| + // TODO(paulberry): handle the case where a file is referred to by more
|
| + // than one context (e.g package A depends on package B using a local
|
| + // path, user has both packages open for editing in separate contexts,
|
| + // and user modifies a file in package B).
|
| + if (analysisContext != null) {
|
| + Source source = getSource(file);
|
| + if (change is AddContentOverlay) {
|
| + analysisContext.setContents(source, change.content);
|
| + } else if (change is ChangeContentOverlay) {
|
| + // TODO(paulberry): an error should be generated if source is not
|
| + // currently in the content cache.
|
| + TimestampedData<String> oldContents =
|
| + analysisContext.getContents(source);
|
| + String newContents;
|
| + try {
|
| + newContents =
|
| + SourceEdit.applySequence(oldContents.data, change.edits);
|
| + } on RangeError {
|
| + throw new RequestFailure(
|
| + new Response(
|
| + id,
|
| + error: new RequestError(
|
| + RequestErrorCode.INVALID_OVERLAY_CHANGE,
|
| + 'Invalid overlay change')));
|
| + }
|
| + // TODO(paulberry): to aid in incremental processing it would be
|
| + // better to use setChangedContents.
|
| + analysisContext.setContents(source, newContents);
|
| + } else if (change is RemoveContentOverlay) {
|
| + analysisContext.setContents(source, null);
|
| + } else {
|
| + // Protocol parsing should have ensured that we never get here.
|
| + throw new AnalysisException('Illegal change type');
|
| + }
|
| + schedulePerformAnalysisOperation(analysisContext);
|
| }
|
| - }
|
| - return elements;
|
| - }
|
| -
|
| - /**
|
| - * Returns a [Future] completing when [file] has been completely analyzed, in
|
| - * particular, all its errors have been computed. The future is completed
|
| - * with an [AnalysisDoneReason] indicating what caused the file's analysis to
|
| - * be considered complete.
|
| - *
|
| - * If the given file doesn't belong to any context, null is returned.
|
| - *
|
| - * TODO(scheglov) this method should be improved.
|
| - *
|
| - * 1. The analysis context should be told to analyze this particular file ASAP.
|
| - *
|
| - * 2. We should complete the future as soon as the file is analyzed (not wait
|
| - * until the context is completely finished)
|
| - */
|
| - Future<AnalysisDoneReason> onFileAnalysisComplete(String file) {
|
| - // prepare AnalysisContext
|
| - AnalysisContext context = getAnalysisContext(file);
|
| - if (context == null) {
|
| - return null;
|
| - }
|
| - // schedule context analysis
|
| - schedulePerformAnalysisOperation(context);
|
| - // associate with the context completer
|
| - Completer<AnalysisDoneReason> completer =
|
| - contextAnalysisDoneCompleters[context];
|
| - if (completer == null) {
|
| - completer = new Completer<AnalysisDoneReason>();
|
| - contextAnalysisDoneCompleters[context] = completer;
|
| - }
|
| - return completer.future;
|
| + });
|
| }
|
|
|
| /**
|
| - * This method is called when analysis of the given [AnalysisContext] is
|
| - * done.
|
| + * Use the given updaters to update the values of the options in every
|
| + * existing analysis context.
|
| */
|
| - void sendContextAnalysisDoneNotifications(AnalysisContext context,
|
| - AnalysisDoneReason reason) {
|
| - Completer<AnalysisDoneReason> completer =
|
| - contextAnalysisDoneCompleters.remove(context);
|
| - if (completer != null) {
|
| - completer.complete(reason);
|
| - }
|
| - }
|
| -
|
| - void shutdown() {
|
| - running = false;
|
| - if (index != null) {
|
| - index.clear();
|
| - index.stop();
|
| - }
|
| - // Defer closing the channel so that the shutdown response can be sent.
|
| - new Future(channel.close);
|
| + void updateOptions(List<OptionUpdater> optionUpdaters) {
|
| + //
|
| + // Update existing contexts.
|
| + //
|
| + folderMap.forEach((Folder folder, AnalysisContext context) {
|
| + AnalysisOptionsImpl options =
|
| + new AnalysisOptionsImpl.con1(context.analysisOptions);
|
| + optionUpdaters.forEach((OptionUpdater optionUpdater) {
|
| + optionUpdater(options);
|
| + });
|
| + context.analysisOptions = options;
|
| + });
|
| + //
|
| + // Update the defaults used to create new contexts.
|
| + //
|
| + AnalysisOptionsImpl options = contextDirectoryManager.defaultOptions;
|
| + optionUpdaters.forEach((OptionUpdater optionUpdater) {
|
| + optionUpdater(options);
|
| + });
|
| }
|
|
|
| /**
|
| @@ -1007,4 +898,111 @@ class AnalysisServer {
|
| }
|
| }
|
|
|
| -typedef void OptionUpdater(AnalysisOptionsImpl options);
|
| +
|
| +/**
|
| + * A [ContextsChangedEvent] indicate what contexts were added or removed.
|
| + *
|
| + * No context should be added to the event more than once. It does not make
|
| + * sense, for example, for a context to be both added and removed.
|
| + */
|
| +class ContextsChangedEvent {
|
| +
|
| + /**
|
| + * The contexts that were added to the server.
|
| + */
|
| + final List<AnalysisContext> added;
|
| +
|
| + /**
|
| + * The contexts that were changed.
|
| + */
|
| + final List<AnalysisContext> changed;
|
| +
|
| + /**
|
| + * The contexts that were removed from the server.
|
| + */
|
| + final List<AnalysisContext> removed;
|
| +
|
| + ContextsChangedEvent({this.added: AnalysisContext.EMPTY_LIST, this.changed:
|
| + AnalysisContext.EMPTY_LIST, this.removed: AnalysisContext.EMPTY_LIST});
|
| +}
|
| +
|
| +class ServerContextManager extends ContextManager {
|
| + final AnalysisServer analysisServer;
|
| +
|
| + /**
|
| + * The default options used to create new analysis contexts.
|
| + */
|
| + AnalysisOptionsImpl defaultOptions = new AnalysisOptionsImpl();
|
| +
|
| + /**
|
| + * The controller for sending [ContextsChangedEvent]s.
|
| + */
|
| + StreamController<ContextsChangedEvent> _onContextsChangedController;
|
| +
|
| + ServerContextManager(this.analysisServer, ResourceProvider resourceProvider,
|
| + PackageMapProvider packageMapProvider)
|
| + : super(resourceProvider, packageMapProvider) {
|
| + _onContextsChangedController = new StreamController<ContextsChangedEvent>();
|
| + }
|
| +
|
| + /**
|
| + * The stream that is notified when contexts are added or removed.
|
| + */
|
| + Stream<ContextsChangedEvent> get onContextsChanged =>
|
| + _onContextsChangedController.stream;
|
| +
|
| + @override
|
| + void addContext(Folder folder, UriResolver packageUriResolver) {
|
| + AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
|
| + analysisServer.folderMap[folder] = context;
|
| + context.sourceFactory = _createSourceFactory(packageUriResolver);
|
| + context.analysisOptions = new AnalysisOptionsImpl.con1(defaultOptions);
|
| + _onContextsChangedController.add(
|
| + new ContextsChangedEvent(added: [context]));
|
| + analysisServer.schedulePerformAnalysisOperation(context);
|
| + }
|
| +
|
| + @override
|
| + void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) {
|
| + AnalysisContext context = analysisServer.folderMap[contextFolder];
|
| + if (context != null) {
|
| + context.applyChanges(changeSet);
|
| + analysisServer.schedulePerformAnalysisOperation(context);
|
| + }
|
| + }
|
| +
|
| + @override
|
| + void removeContext(Folder folder) {
|
| + AnalysisContext context = analysisServer.folderMap.remove(folder);
|
| + if (analysisServer.index != null) {
|
| + analysisServer.index.removeContext(context);
|
| + }
|
| + _onContextsChangedController.add(
|
| + new ContextsChangedEvent(removed: [context]));
|
| + analysisServer.sendContextAnalysisDoneNotifications(
|
| + context,
|
| + AnalysisDoneReason.CONTEXT_REMOVED);
|
| + }
|
| +
|
| + @override
|
| + void updateContextPackageUriResolver(Folder contextFolder,
|
| + UriResolver packageUriResolver) {
|
| + AnalysisContext context = analysisServer.folderMap[contextFolder];
|
| + context.sourceFactory = _createSourceFactory(packageUriResolver);
|
| + _onContextsChangedController.add(
|
| + new ContextsChangedEvent(changed: [context]));
|
| + analysisServer.schedulePerformAnalysisOperation(context);
|
| + }
|
| +
|
| + /**
|
| + * Set up a [SourceFactory] that resolves packages using the given
|
| + * [packageUriResolver].
|
| + */
|
| + SourceFactory _createSourceFactory(UriResolver packageUriResolver) {
|
| + List<UriResolver> resolvers = <UriResolver>[
|
| + new DartUriResolver(analysisServer.defaultSdk),
|
| + new ResourceUriResolver(resourceProvider),
|
| + packageUriResolver];
|
| + return new SourceFactory(resolvers);
|
| + }
|
| +}
|
|
|