Add OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT_ID.

R=brianwilkerson@google.com
BUG=

Review URL: https://codereview.chromium.org//1337143002 .
This commit is contained in:
Konstantin Shcheglov
2015-09-11 13:59:28 -07:00
parent b0ca0789be
commit ef2fc0d778
14 changed files with 323 additions and 130 deletions
@@ -2,7 +2,7 @@
// 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 analysis_server.analysis.navigation.navigation_core;
library analysis_server.analysis.navigation_core;
import 'package:analysis_server/src/protocol.dart'
show ElementKind, Location, NavigationRegion, NavigationTarget;
@@ -0,0 +1,35 @@
// Copyright (c) 2015, 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 analysis_server.analysis.occurrences_core;
import 'package:analysis_server/src/protocol.dart' show Element, Occurrences;
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
import 'package:analyzer/src/generated/source.dart' show Source;
/**
* An object used to produce occurrences.
*
* Clients are expected to subtype this class when implementing plugins.
*/
abstract class OccurrencesContributor {
/**
* Contribute occurrences into the given [collector].
* The [context] can be used to get analysis results.
*/
void computeOccurrences(
OccurrencesCollector collector, AnalysisContext context, Source source);
}
/**
* An object that [OccurrencesContributor]s use to record occurrences into.
*
* Clients are not expected to subtype this class.
*/
abstract class OccurrencesCollector {
/**
* Record a new element occurrences.
*/
void addOccurrences(Occurrences occurrences);
}
@@ -8,7 +8,7 @@
*/
library analysis_server.plugin.navigation;
import 'package:analysis_server/analysis/navigation/navigation_core.dart';
import 'package:analysis_server/analysis/navigation_core.dart';
import 'package:analysis_server/src/plugin/server_plugin.dart';
import 'package:plugin/plugin.dart';
@@ -0,0 +1,22 @@
// Copyright (c) 2015, 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.
/**
* Support for client code that extends the analysis server by adding new
* occurrences contributors.
*/
library analysis_server.plugin.occurrences;
import 'package:analysis_server/analysis/occurrences_core.dart';
import 'package:analysis_server/src/plugin/server_plugin.dart';
import 'package:plugin/plugin.dart';
/**
* The identifier of the extension point that allows plugins to register
* element occurrences. The object used as an extension must be
* a [OccurrencesContributor].
*/
final String OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT_ID = Plugin.join(
ServerPlugin.UNIQUE_IDENTIFIER,
ServerPlugin.OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT);
@@ -975,7 +975,7 @@ class AnalysisServer {
sendAnalysisNotificationNavigation(this, context, source);
break;
case AnalysisService.OCCURRENCES:
sendAnalysisNotificationOccurrences(this, file, dartUnit);
sendAnalysisNotificationOccurrences(this, context, source);
break;
case AnalysisService.OUTLINE:
AnalysisContext context = dartUnit.element.context;
@@ -1,78 +0,0 @@
// 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 computer.occurrences;
import 'dart:collection';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
/**
* A computer for elements occurrences in a Dart [CompilationUnit].
*/
class DartUnitOccurrencesComputer {
final CompilationUnit _unit;
final Map<Element, List<int>> _elementsOffsets =
new HashMap<Element, List<int>>();
DartUnitOccurrencesComputer(this._unit);
/**
* Returns the computed occurrences, not `null`.
*/
List<protocol.Occurrences> compute() {
_unit.accept(new _DartUnitOccurrencesComputerVisitor(this));
List<protocol.Occurrences> occurrences = <protocol.Occurrences>[];
_elementsOffsets.forEach((engineElement, offsets) {
var serverElement = protocol.newElement_fromEngine(engineElement);
var length = engineElement.displayName.length;
occurrences.add(new protocol.Occurrences(serverElement, offsets, length));
});
return occurrences;
}
void _addOccurrence(Element element, int offset) {
element = _canonicalizeElement(element);
if (element == null || element == DynamicElementImpl.instance) {
return;
}
List<int> offsets = _elementsOffsets[element];
if (offsets == null) {
offsets = <int>[];
_elementsOffsets[element] = offsets;
}
offsets.add(offset);
}
Element _canonicalizeElement(Element element) {
if (element is FieldFormalParameterElement) {
element = (element as FieldFormalParameterElement).field;
}
if (element is PropertyAccessorElement) {
element = (element as PropertyAccessorElement).variable;
}
if (element is Member) {
element = (element as Member).baseElement;
}
return element;
}
}
class _DartUnitOccurrencesComputerVisitor extends RecursiveAstVisitor {
final DartUnitOccurrencesComputer computer;
_DartUnitOccurrencesComputerVisitor(this.computer);
@override
visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.bestElement;
if (element != null) {
computer._addOccurrence(element, node.offset);
}
return super.visitSimpleIdentifier(node);
}
}
@@ -14,7 +14,10 @@ import 'package:analysis_server/src/constants.dart';
import 'package:analysis_server/src/context_manager.dart';
import 'package:analysis_server/src/domains/analysis/navigation.dart';
import 'package:analysis_server/src/operation/operation_analysis.dart'
show NavigationOperation, sendAnalysisNotificationNavigation;
show
NavigationOperation,
OccurrencesOperation,
sendAnalysisNotificationNavigation;
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/dependencies/library_dependencies.dart';
import 'package:analyzer/file_system/file_system.dart';
@@ -352,6 +355,9 @@ class AnalysisDomainImpl implements AnalysisDomain {
if (service == AnalysisService.NAVIGATION) {
server.scheduleOperation(new NavigationOperation(context, source));
}
if (service == AnalysisService.OCCURRENCES) {
server.scheduleOperation(new OccurrencesOperation(context, source));
}
}
}
@@ -1,4 +1,4 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// Copyright (c) 2015, 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.
@@ -6,7 +6,7 @@ library domains.analysis.navigation;
import 'dart:collection';
import 'package:analysis_server/analysis/navigation/navigation_core.dart';
import 'package:analysis_server/analysis/navigation_core.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/collections.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
@@ -1,10 +1,10 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// Copyright (c) 2015, 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 domains.analysis.navigation_dart;
import 'package:analysis_server/analysis/navigation/navigation_core.dart';
import 'package:analysis_server/analysis/navigation_core.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
@@ -0,0 +1,45 @@
// Copyright (c) 2015, 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 domains.analysis.occurrences;
import 'package:analysis_server/analysis/occurrences_core.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analyzer/src/generated/engine.dart'
show AnalysisContext, AnalysisEngine;
import 'package:analyzer/src/generated/java_engine.dart' show CaughtException;
import 'package:analyzer/src/generated/source.dart' show Source;
/**
* Compute all known occurrences for the given [source].
*/
OccurrencesCollectorImpl computeOccurrences(
AnalysisServer server, AnalysisContext context, Source source) {
OccurrencesCollectorImpl collector = new OccurrencesCollectorImpl();
List<OccurrencesContributor> contributors =
server.serverPlugin.occurrencesContributors;
for (OccurrencesContributor contributor in contributors) {
try {
contributor.computeOccurrences(collector, context, source);
} catch (exception, stackTrace) {
AnalysisEngine.instance.logger.logError(
'Exception from occurrences contributor: ${contributor.runtimeType}',
new CaughtException(exception, stackTrace));
}
}
return collector;
}
/**
* A concrete implementation of [OccurrencesCollector].
*/
class OccurrencesCollectorImpl implements OccurrencesCollector {
final List<protocol.Occurrences> allOccurrences = <protocol.Occurrences>[];
@override
void addOccurrences(protocol.Occurrences occurrences) {
allOccurrences.add(occurrences);
}
}
@@ -0,0 +1,79 @@
// Copyright (c) 2015, 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 domains.analysis.occurrences_dart;
import 'package:analysis_server/analysis/occurrences_core.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
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';
/**
* A computer for occurrences in a Dart [CompilationUnit].
*/
class DartOccurrencesComputer implements OccurrencesContributor {
@override
void computeOccurrences(
OccurrencesCollector collector, AnalysisContext context, Source source) {
List<Source> libraries = context.getLibrariesContaining(source);
if (libraries.isNotEmpty) {
CompilationUnit unit =
context.getResolvedCompilationUnit2(source, libraries.first);
if (unit != null) {
_DartUnitOccurrencesComputerVisitor visitor =
new _DartUnitOccurrencesComputerVisitor();
unit.accept(visitor);
visitor.elementsOffsets.forEach((engineElement, offsets) {
int length = engineElement.displayName.length;
protocol.Element serverElement =
protocol.newElement_fromEngine(engineElement);
protocol.Occurrences occurrences =
new protocol.Occurrences(serverElement, offsets, length);
collector.addOccurrences(occurrences);
});
}
}
}
}
class _DartUnitOccurrencesComputerVisitor extends RecursiveAstVisitor {
final Map<Element, List<int>> elementsOffsets = <Element, List<int>>{};
@override
visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.bestElement;
if (element != null) {
_addOccurrence(element, node.offset);
}
return super.visitSimpleIdentifier(node);
}
void _addOccurrence(Element element, int offset) {
element = _canonicalizeElement(element);
if (element == null || element == DynamicElementImpl.instance) {
return;
}
List<int> offsets = elementsOffsets[element];
if (offsets == null) {
offsets = <int>[];
elementsOffsets[element] = offsets;
}
offsets.add(offset);
}
Element _canonicalizeElement(Element element) {
if (element is FieldFormalParameterElement) {
element = (element as FieldFormalParameterElement).field;
}
if (element is PropertyAccessorElement) {
element = (element as PropertyAccessorElement).variable;
}
if (element is Member) {
element = (element as Member).baseElement;
}
return element;
}
}
@@ -7,10 +7,10 @@ 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_occurrences.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/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';
@@ -82,8 +82,8 @@ void scheduleNotificationOperations(
}
if (server.hasAnalysisSubscription(
protocol.AnalysisService.OCCURRENCES, file)) {
server.scheduleOperation(
new _DartOccurrencesOperation(context, file, resolvedDartUnit));
Source source = resolvedDartUnit.element.source;
server.scheduleOperation(new OccurrencesOperation(context, source));
}
if (server.hasAnalysisSubscription(
protocol.AnalysisService.OVERRIDES, file)) {
@@ -179,10 +179,13 @@ void sendAnalysisNotificationNavigation(
}
void sendAnalysisNotificationOccurrences(
AnalysisServer server, String file, CompilationUnit dartUnit) {
AnalysisServer server, AnalysisContext context, Source source) {
_sendNotification(server, () {
var occurrences = new DartUnitOccurrencesComputer(dartUnit).compute();
var params = new protocol.AnalysisOccurrencesParams(file, occurrences);
OccurrencesCollectorImpl collector =
computeOccurrences(server, context, source);
String file = source.fullName;
var params =
new protocol.AnalysisOccurrencesParams(file, collector.allOccurrences);
server.sendNotification(params.toNotification());
});
}
@@ -234,17 +237,35 @@ class NavigationOperation extends _NotificationOperation
NavigationOperation(AnalysisContext context, Source source)
: super(context, source);
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationNavigation(server, 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);
}
}
/**
@@ -410,17 +431,6 @@ abstract class _DartNotificationOperation extends _SingleFileOperation {
}
}
class _DartOccurrencesOperation extends _DartNotificationOperation {
_DartOccurrencesOperation(
AnalysisContext context, String file, CompilationUnit unit)
: super(context, file, unit);
@override
void perform(AnalysisServer server) {
sendAnalysisNotificationOccurrences(server, file, unit);
}
}
class _DartOutlineOperation extends _DartNotificationOperation {
final LineInfo lineInfo;
@@ -6,7 +6,8 @@ library analysis_server.src.plugin.server_plugin;
import 'package:analysis_server/analysis/analysis_domain.dart';
import 'package:analysis_server/analysis/index/index_core.dart';
import 'package:analysis_server/analysis/navigation/navigation_core.dart';
import 'package:analysis_server/analysis/navigation_core.dart';
import 'package:analysis_server/analysis/occurrences_core.dart';
import 'package:analysis_server/completion/completion_core.dart';
import 'package:analysis_server/edit/assist/assist_core.dart';
import 'package:analysis_server/edit/fix/fix_core.dart';
@@ -14,12 +15,14 @@ import 'package:analysis_server/plugin/analyzed_files.dart';
import 'package:analysis_server/plugin/assist.dart';
import 'package:analysis_server/plugin/fix.dart';
import 'package:analysis_server/plugin/navigation.dart';
import 'package:analysis_server/plugin/occurrences.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/domain_analysis.dart';
import 'package:analysis_server/src/domain_completion.dart';
import 'package:analysis_server/src/domain_execution.dart';
import 'package:analysis_server/src/domain_server.dart';
import 'package:analysis_server/src/domains/analysis/navigation_dart.dart';
import 'package:analysis_server/src/domains/analysis/occurrences_dart.dart';
import 'package:analysis_server/src/edit/edit_domain.dart';
import 'package:analysis_server/src/protocol.dart';
import 'package:analysis_server/src/search/search_domain.dart';
@@ -84,6 +87,13 @@ class ServerPlugin implements Plugin {
static const String NAVIGATION_CONTRIBUTOR_EXTENSION_POINT =
'navigationContributor';
/**
* The simple identifier of the extension point that allows plugins to
* register element occurrences.
*/
static const String OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT =
'occurrencesContributor';
/**
* The simple identifier of the extension point that allows plugins to
* register analysis result listeners.
@@ -130,10 +140,17 @@ class ServerPlugin implements Plugin {
ExtensionPoint indexContributorExtensionPoint;
/**
* The extension point that allows plugins to register navigation contributors.
* The extension point that allows plugins to register navigation
* contributors.
*/
ExtensionPoint navigationContributorExtensionPoint;
/**
* The extension point that allows plugins to register occurrences
* contributors.
*/
ExtensionPoint occurrencesContributorExtensionPoint;
/**
* The extension point that allows plugins to get access to the `analysis`
* domain.
@@ -186,6 +203,13 @@ class ServerPlugin implements Plugin {
List<NavigationContributor> get navigationContributors =>
navigationContributorExtensionPoint.extensions;
/**
* Return a list containing all of the occurrences contributors that were
* contributed.
*/
List<OccurrencesContributor> get occurrencesContributors =>
occurrencesContributorExtensionPoint.extensions;
/**
* Return a list containing all of the receivers of the `analysis` domain
* instance.
@@ -231,6 +255,9 @@ class ServerPlugin implements Plugin {
navigationContributorExtensionPoint = registerExtensionPoint(
NAVIGATION_CONTRIBUTOR_EXTENSION_POINT,
_validateNavigationContributorExtension);
occurrencesContributorExtensionPoint = registerExtensionPoint(
OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT,
_validateOccurrencesContributorExtension);
}
@override
@@ -253,10 +280,12 @@ class ServerPlugin implements Plugin {
// TODO(brianwilkerson) Register the completion contributors.
// registerExtension(COMPLETION_CONTRIBUTOR_EXTENSION_POINT_ID, ???);
//
// Register navigation contributors.
// Register analysis contributors.
//
registerExtension(NAVIGATION_CONTRIBUTOR_EXTENSION_POINT_ID,
new DartNavigationComputer());
registerExtension(OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT_ID,
new DartOccurrencesComputer());
//
// Register domains.
//
@@ -367,6 +396,18 @@ class ServerPlugin implements Plugin {
}
}
/**
* Validate the given extension by throwing an [ExtensionError] if it is not a
* valid occurrences contributor.
*/
void _validateOccurrencesContributorExtension(Object extension) {
if (extension is! OccurrencesContributor) {
String id = occurrencesContributorExtensionPoint.uniqueIdentifier;
throw new ExtensionError(
'Extensions to $id must be an OccurrencesContributor');
}
}
/**
* Validate the given extension by throwing an [ExtensionError] if it is not a
* valid analysis domain receiver.
@@ -7,8 +7,10 @@ library test.plugin.analysis_contributor;
import 'dart:async';
import 'package:analysis_server/analysis/analysis_domain.dart';
import 'package:analysis_server/analysis/navigation/navigation_core.dart';
import 'package:analysis_server/analysis/navigation_core.dart';
import 'package:analysis_server/analysis/occurrences_core.dart';
import 'package:analysis_server/plugin/navigation.dart';
import 'package:analysis_server/plugin/occurrences.dart';
import 'package:analysis_server/src/constants.dart';
import 'package:analysis_server/src/protocol.dart';
import 'package:analyzer/src/generated/engine.dart';
@@ -38,9 +40,8 @@ main() {
class SetAnalysisDomainTest extends AbstractAnalysisTest {
final Set<String> parsedUnitFiles = new Set<String>();
List<NavigationRegion> regions;
List<NavigationTarget> targets;
List<String> targetFiles;
AnalysisNavigationParams navigationParams;
AnalysisOccurrencesParams occurrencesParams;
@override
void addServerPlugins(List<Plugin> plugins) {
@@ -53,9 +54,13 @@ class SetAnalysisDomainTest extends AbstractAnalysisTest {
if (notification.event == ANALYSIS_NAVIGATION) {
var params = new AnalysisNavigationParams.fromNotification(notification);
if (params.file == testFile) {
regions = params.regions;
targets = params.targets;
targetFiles = params.files;
navigationParams = params;
}
}
if (notification.event == ANALYSIS_OCCURRENCES) {
var params = new AnalysisOccurrencesParams.fromNotification(notification);
if (params.file == testFile) {
occurrencesParams = params;
}
}
}
@@ -63,27 +68,38 @@ class SetAnalysisDomainTest extends AbstractAnalysisTest {
Future test_contributorIsInvoked() async {
createProject();
addAnalysisSubscription(AnalysisService.NAVIGATION, testFile);
addAnalysisSubscription(AnalysisService.OCCURRENCES, testFile);
addTestFile('// usually no navigation');
await server.onAnalysisComplete;
// we have PARSED_UNIT
expect(parsedUnitFiles, contains(testFile));
// we have an additional navigation region/target
expect(regions, hasLength(1));
{
NavigationRegion region = regions.single;
expect(region.offset, 1);
expect(region.length, 5);
expect(region.targets.single, 0);
expect(navigationParams.regions, hasLength(1));
{
NavigationRegion region = navigationParams.regions.single;
expect(region.offset, 1);
expect(region.length, 5);
expect(region.targets.single, 0);
}
{
NavigationTarget target = navigationParams.targets.single;
expect(target.fileIndex, 0);
expect(target.offset, 1);
expect(target.length, 2);
expect(target.startLine, 3);
expect(target.startColumn, 4);
}
expect(navigationParams.files.single, '/testLocation.dart');
}
// we have additional occurrences
{
NavigationTarget target = targets.single;
expect(target.fileIndex, 0);
expect(target.offset, 1);
expect(target.length, 2);
expect(target.startLine, 3);
expect(target.startColumn, 4);
expect(occurrencesParams.occurrences, hasLength(1));
Occurrences occurrences = occurrencesParams.occurrences.single;
expect(occurrences.element.name, 'TestElement');
expect(occurrences.length, 5);
expect(occurrences.offsets, unorderedEquals([1, 2, 3]));
}
expect(targetFiles.single, '/testLocation.dart');
}
}
@@ -100,6 +116,19 @@ class TestNavigationContributor implements NavigationContributor {
}
}
class TestOccurrencesContributor implements OccurrencesContributor {
final SetAnalysisDomainTest test;
TestOccurrencesContributor(this.test);
@override
void computeOccurrences(
OccurrencesCollector collector, AnalysisContext context, Source source) {
collector.addOccurrences(new Occurrences(
new Element(ElementKind.UNKNOWN, 'TestElement', 0), <int>[1, 2, 3], 5));
}
}
class TestSetAnalysisDomainPlugin implements Plugin {
final SetAnalysisDomainTest test;
@@ -116,6 +145,8 @@ class TestSetAnalysisDomainPlugin implements Plugin {
register(SET_ANALYSIS_DOMAIN_EXTENSION_POINT_ID, _setAnalysisDomain);
register(NAVIGATION_CONTRIBUTOR_EXTENSION_POINT_ID,
new TestNavigationContributor(test));
register(OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT_ID,
new TestOccurrencesContributor(test));
}
void _setAnalysisDomain(AnalysisDomain domain) {
@@ -127,6 +158,8 @@ class TestSetAnalysisDomainPlugin implements Plugin {
test.parsedUnitFiles.add(source.fullName);
domain.scheduleNotification(
result.context, source, AnalysisService.NAVIGATION);
domain.scheduleNotification(
result.context, source, AnalysisService.OCCURRENCES);
});
}
}