blob: aeb8d039482244714822f9f90b4637bf65a41eb4 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library operation.analysis;
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/computer/computer_highlights.dart';
import 'package:analysis_server/src/computer/computer_highlights2.dart';
import 'package:analysis_server/src/computer/computer_outline.dart';
import 'package:analysis_server/src/computer/computer_overrides.dart';
import 'package:analysis_server/src/domains/analysis/implemented_dart.dart';
import 'package:analysis_server/src/domains/analysis/navigation.dart';
import 'package:analysis_server/src/domains/analysis/occurrences.dart';
import 'package:analysis_server/src/operation/operation.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analysis_server/src/services/dependencies/library_dependencies.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/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/html.dart';
import 'package:analyzer/src/generated/source.dart';
/**
* Runs the given function [f] with the working cache size in [context].
* Returns the result of [f] invocation.
*/
runWithWorkingCacheSize(AnalysisContext context, f()) {
int currentCacheSize = context.analysisOptions.cacheSize;
if (currentCacheSize < PerformAnalysisOperation.WORKING_CACHE_SIZE) {
setCacheSize(context, PerformAnalysisOperation.WORKING_CACHE_SIZE);
try {
return f();
} finally {
setCacheSize(context, currentCacheSize);
}
} else {
return f();
}
}
scheduleImplementedNotification(
AnalysisServer server, Iterable<String> files) async {
SearchEngine searchEngine = server.searchEngine;
if (searchEngine == null) {
return;
}
for (String file in files) {
CompilationUnitElement unitElement = server.getCompilationUnitElement(file);
if (unitElement != null) {
ImplementedComputer computer =
new ImplementedComputer(searchEngine, unitElement);
await computer.compute();
var params = new protocol.AnalysisImplementedParams(
file, computer.classes, computer.members);
server.sendNotification(params.toNotification());
}
}
}
/**
* Schedules indexing of the given [file] using the resolved [dartUnit].
*/
void scheduleIndexOperation(
AnalysisServer server, String file, CompilationUnit dartUnit) {
if (server.index != null) {
AnalysisContext context = dartUnit.element.context;
server.addOperation(new _DartIndexOperation(context, file, dartUnit));
}
}
/**
* Schedules sending notifications for the given [file] using the resolved
* [resolvedDartUnit].
*/
void scheduleNotificationOperations(
AnalysisServer server,
Source source,
String file,
LineInfo lineInfo,
AnalysisContext context,
CompilationUnit parsedDartUnit,
CompilationUnit resolvedDartUnit,
List<AnalysisError> errors) {
// If the file belongs to any analysis root, check whether we're in it now.
AnalysisContext containingContext = server.getContainingContext(file);
if (containingContext != null && context != containingContext) {
return;
}
// Dart
CompilationUnit dartUnit =
resolvedDartUnit != null ? resolvedDartUnit : parsedDartUnit;
if (resolvedDartUnit != null) {
if (server.hasAnalysisSubscription(
protocol.AnalysisService.HIGHLIGHTS, file)) {
server.scheduleOperation(
new _DartHighlightsOperation(context, file, resolvedDartUnit));
}
if (server.hasAnalysisSubscription(
protocol.AnalysisService.NAVIGATION, file)) {
server.scheduleOperation(new NavigationOperation(context, source));
}
if (server.hasAnalysisSubscription(
protocol.AnalysisService.OCCURRENCES, file)) {
server.scheduleOperation(new OccurrencesOperation(context, source));
}
if (server.hasAnalysisSubscription(
protocol.AnalysisService.OVERRIDES, file)) {
server.scheduleOperation(
new _DartOverridesOperation(context, file, resolvedDartUnit));
}
}
if (dartUnit != null) {
if (server.hasAnalysisSubscription(
protocol.AnalysisService.OUTLINE, file)) {
SourceKind sourceKind = context.getKindOf(source);
server.scheduleOperation(new _DartOutlineOperation(
context, file, lineInfo, sourceKind, dartUnit));
}
}
// errors
if (server.shouldSendErrorsNotificationFor(file)) {
server.scheduleOperation(
new _NotificationErrorsOperation(context, file, lineInfo, errors));
}
}
void sendAnalysisNotificationAnalyzedFiles(AnalysisServer server) {
_sendNotification(server, () {
// TODO(paulberry): if it proves to be too inefficient to recompute the set
// of analyzed files each time analysis is complete, consider modifying the
// analysis engine to update this set incrementally as analysis is
// performed.
LibraryDependencyCollector collector =
new LibraryDependencyCollector(server.getAnalysisContexts().toList());
Set<String> analyzedFiles = collector.collectLibraryDependencies();
Set<String> prevAnalyzedFiles = server.prevAnalyzedFiles;
if (prevAnalyzedFiles != null &&
prevAnalyzedFiles.length == analyzedFiles.length &&
prevAnalyzedFiles.difference(analyzedFiles).isEmpty) {
// No change to the set of analyzed files. No need to send another
// notification.
return;
}
server.prevAnalyzedFiles = analyzedFiles;
protocol.AnalysisAnalyzedFilesParams params =
new protocol.AnalysisAnalyzedFilesParams(analyzedFiles.toList());
server.sendNotification(params.toNotification());
});
}
void sendAnalysisNotificationErrors(AnalysisServer server, String file,
LineInfo lineInfo, List<AnalysisError> errors) {
_sendNotification(server, () {
if (errors == null) {
errors = <AnalysisError>[];
}
var serverErrors =
protocol.doAnalysisError_listFromEngine(lineInfo, errors);
var params = new protocol.AnalysisErrorsParams(file, serverErrors);
server.sendNotification(params.toNotification());
});
}
void sendAnalysisNotificationFlushResults(
AnalysisServer server, List<String> files) {
_sendNotification(server, () {
if (files != null && files.isNotEmpty) {
var params = new protocol.AnalysisFlushResultsParams(files);
server.sendNotification(params.toNotification());
}
});
}
void sendAnalysisNotificationHighlights(
AnalysisServer server, String file, CompilationUnit dartUnit) {
_sendNotification(server, () {
List<protocol.HighlightRegion> regions;
if (server.options.useAnalysisHighlight2) {
regions = new DartUnitHighlightsComputer2(dartUnit).compute();
} else {
regions = new DartUnitHighlightsComputer(dartUnit).compute();
}
var params = new protocol.AnalysisHighlightsParams(file, regions);
server.sendNotification(params.toNotification());
});
}
void sendAnalysisNotificationNavigation(
AnalysisServer server, AnalysisContext context, Source source) {
_sendNotification(server, () {
NavigationCollectorImpl collector =
computeNavigation(server, context, source, null, null);
String file = source.fullName;
var params = new protocol.AnalysisNavigationParams(
file, collector.regions, collector.targets, collector.files);
server.sendNotification(params.toNotification());
});
}
void sendAnalysisNotificationOccurrences(
AnalysisServer server, AnalysisContext context, Source source) {
_sendNotification(server, () {
OccurrencesCollectorImpl collector =
computeOccurrences(server, context, source);
String file = source.fullName;
var params =
new protocol.AnalysisOccurrencesParams(file, collector.allOccurrences);
server.sendNotification(params.toNotification());
});
}
void sendAnalysisNotificationOutline(AnalysisServer server, String file,
LineInfo lineInfo, SourceKind sourceKind, CompilationUnit dartUnit) {
_sendNotification(server, () {
// compute FileKind
protocol.FileKind fileKind = protocol.FileKind.LIBRARY;
if (sourceKind == SourceKind.LIBRARY) {
fileKind = protocol.FileKind.LIBRARY;
} else if (sourceKind == SourceKind.PART) {
fileKind = protocol.FileKind.PART;
}
// compute library name
String libraryName = _computeLibraryName(dartUnit);
// compute Outline
var computer = new DartUnitOutlineComputer(file, lineInfo, dartUnit);
protocol.Outline outline = computer.compute();
// send notification
var params = new protocol.AnalysisOutlineParams(file, fileKind, outline,
libraryName: libraryName);
server.sendNotification(params.toNotification());
});
}
void sendAnalysisNotificationOverrides(
AnalysisServer server, String file, CompilationUnit dartUnit) {
_sendNotification(server, () {
var overrides = new DartUnitOverridesComputer(dartUnit).compute();
var params = new protocol.AnalysisOverridesParams(file, overrides);
server.sendNotification(params.toNotification());
});
}
/**
* Sets the cache size in the given [context] to the given value.
*/
void setCacheSize(AnalysisContext context, int cacheSize) {
AnalysisOptionsImpl options =
new AnalysisOptionsImpl.from(context.analysisOptions);
options.cacheSize = cacheSize;
context.analysisOptions = options;
}
String _computeLibraryName(CompilationUnit unit) {
for (Directive directive in unit.directives) {
if (directive is LibraryDirective && directive.name != null) {
return directive.name.name;
}
}
for (Directive directive in unit.directives) {
if (directive is PartOfDirective && directive.libraryName != null) {
return directive.libraryName.name;
}
}
return null;
}
/**
* Runs the given notification producing function [f], catching exceptions.
*/
void _sendNotification(AnalysisServer server, f()) {
ServerPerformanceStatistics.notices.makeCurrentWhile(() {
try {
f();
} catch (exception, stackTrace) {
server.sendServerErrorNotification(
'Failed to send notification', exception, stackTrace);
}
});
}
class NavigationOperation extends _NotificationOperation
implements MergeableOperation {
NavigationOperation(AnalysisContext context, Source source)
: super(context, source);
@override
bool merge(ServerOperation other) {
return other is NavigationOperation &&
other.context == context &&
other.source == source;
}
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationNavigation(server, context, source);
}
}
class OccurrencesOperation extends _NotificationOperation
implements MergeableOperation {
OccurrencesOperation(AnalysisContext context, Source source)
: super(context, source);
@override
bool merge(ServerOperation other) {
return other is OccurrencesOperation &&
other.context == context &&
other.source == source;
}
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationOccurrences(server, context, source);
}
}
/**
* Instances of [PerformAnalysisOperation] perform a single analysis task.
*/
class PerformAnalysisOperation extends ServerOperation {
static const int IDLE_CACHE_SIZE = AnalysisOptionsImpl.DEFAULT_CACHE_SIZE;
static const int WORKING_CACHE_SIZE = 512;
final bool isContinue;
PerformAnalysisOperation(AnalysisContext context, this.isContinue)
: super(context);
@override
ServerOperationPriority get priority {
if (_isPriorityContext) {
if (isContinue) {
return ServerOperationPriority.PRIORITY_ANALYSIS_CONTINUE;
} else {
return ServerOperationPriority.PRIORITY_ANALYSIS;
}
} else {
if (isContinue) {
return ServerOperationPriority.ANALYSIS_CONTINUE;
} else {
return ServerOperationPriority.ANALYSIS;
}
}
}
bool get _isPriorityContext => context is InternalAnalysisContext &&
(context as InternalAnalysisContext).prioritySources.isNotEmpty;
@override
void perform(AnalysisServer server) {
//
// TODO(brianwilkerson) Add an optional function-valued parameter to
// performAnalysisTask that will be called when the task has been computed
// but before it is performed and send notification in the function:
//
// AnalysisResult result = context.performAnalysisTask((taskDescription) {
// sendStatusNotification(context.toString(), taskDescription);
// });
if (!isContinue) {
setCacheSize(context, WORKING_CACHE_SIZE);
}
// prepare results
AnalysisResult result = context.performAnalysisTask();
List<ChangeNotice> notices = result.changeNotices;
// nothing to analyze
if (notices == null) {
bool cacheInconsistencyFixed = context.validateCacheConsistency();
if (cacheInconsistencyFixed) {
server.addOperation(new PerformAnalysisOperation(context, true));
return;
}
// analysis is done
setCacheSize(context, IDLE_CACHE_SIZE);
server.sendContextAnalysisDoneNotifications(
context, AnalysisDoneReason.COMPLETE);
return;
}
// process results
ServerPerformanceStatistics.notices.makeCurrentWhile(() {
_sendNotices(server, notices);
_updateIndex(server, notices);
});
// continue analysis
server.addOperation(new PerformAnalysisOperation(context, true));
}
/**
* Send the information in the given list of notices back to the client.
*/
void _sendNotices(AnalysisServer server, List<ChangeNotice> notices) {
for (int i = 0; i < notices.length; i++) {
ChangeNotice notice = notices[i];
Source source = notice.source;
String file = source.fullName;
// Dart
CompilationUnit parsedDartUnit = notice.parsedDartUnit;
CompilationUnit resolvedDartUnit = notice.resolvedDartUnit;
scheduleNotificationOperations(server, source, file, notice.lineInfo,
context, parsedDartUnit, resolvedDartUnit, notice.errors);
// done
server.fileAnalyzed(notice);
}
}
void _updateIndex(AnalysisServer server, List<ChangeNotice> notices) {
if (server.index == null) {
return;
}
for (ChangeNotice notice in notices) {
String file = notice.source.fullName;
// Dart
try {
CompilationUnit dartUnit = notice.resolvedDartUnit;
if (dartUnit != null) {
scheduleIndexOperation(server, file, dartUnit);
}
} catch (exception, stackTrace) {
server.sendServerErrorNotification(
'Failed to index Dart file: $file', exception, stackTrace);
}
// HTML
try {
HtmlUnit htmlUnit = notice.resolvedHtmlUnit;
if (htmlUnit != null) {
server.addOperation(new _HtmlIndexOperation(context, file, htmlUnit));
}
} catch (exception, stackTrace) {
server.sendServerErrorNotification(
'Failed to index HTML file: $file', exception, stackTrace);
}
}
}
}
class _DartHighlightsOperation extends _DartNotificationOperation {
_DartHighlightsOperation(
AnalysisContext context, String file, CompilationUnit unit)
: super(context, file, unit);
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationHighlights(server, file, unit);
}
}
class _DartIndexOperation extends _SingleFileOperation {
final CompilationUnit unit;
_DartIndexOperation(AnalysisContext context, String file, this.unit)
: super(context, file);
@override
ServerOperationPriority get priority {
return ServerOperationPriority.ANALYSIS_INDEX;
}
@override
void perform(AnalysisServer server) {
ServerPerformanceStatistics.indexOperation.makeCurrentWhile(() {
try {
Index index = server.index;
AnalysisContext context = unit.element.context;
index.index(context, unit);
} catch (exception, stackTrace) {
server.sendServerErrorNotification(
'Failed to index: $file', exception, stackTrace);
}
});
}
}
abstract class _DartNotificationOperation extends _SingleFileOperation {
final CompilationUnit unit;
_DartNotificationOperation(AnalysisContext context, String file, this.unit)
: super(context, file);
@override
ServerOperationPriority get priority {
return ServerOperationPriority.ANALYSIS_NOTIFICATION;
}
}
class _DartOutlineOperation extends _DartNotificationOperation {
final LineInfo lineInfo;
final SourceKind sourceKind;
_DartOutlineOperation(AnalysisContext context, String file, this.lineInfo,
this.sourceKind, CompilationUnit unit)
: super(context, file, unit);
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationOutline(server, file, lineInfo, sourceKind, unit);
}
}
class _DartOverridesOperation extends _DartNotificationOperation {
_DartOverridesOperation(
AnalysisContext context, String file, CompilationUnit unit)
: super(context, file, unit);
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationOverrides(server, file, unit);
}
}
class _HtmlIndexOperation extends _SingleFileOperation {
final HtmlUnit unit;
_HtmlIndexOperation(AnalysisContext context, String file, this.unit)
: super(context, file);
@override
ServerOperationPriority get priority {
return ServerOperationPriority.ANALYSIS_INDEX;
}
@override
void perform(AnalysisServer server) {
Index index = server.index;
index.index(context, unit);
}
}
class _NotificationErrorsOperation extends _SingleFileOperation {
final LineInfo lineInfo;
final List<AnalysisError> errors;
_NotificationErrorsOperation(
AnalysisContext context, String file, this.lineInfo, this.errors)
: super(context, file);
@override
ServerOperationPriority get priority {
return ServerOperationPriority.ANALYSIS_NOTIFICATION;
}
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationErrors(server, file, lineInfo, errors);
}
}
abstract class _NotificationOperation extends SourceSensitiveOperation {
final Source source;
_NotificationOperation(AnalysisContext context, this.source) : super(context);
@override
ServerOperationPriority get priority {
return ServerOperationPriority.ANALYSIS_NOTIFICATION;
}
@override
bool shouldBeDiscardedOnSourceChange(Source source) {
return source == this.source;
}
}
abstract class _SingleFileOperation extends SourceSensitiveOperation {
final String file;
_SingleFileOperation(AnalysisContext context, this.file) : super(context);
@override
bool shouldBeDiscardedOnSourceChange(Source source) {
return source.fullName == file;
}
}