Analysis request getReachableSources (#24893).

Implements a new request to get reachable sources.

This solves a specific issue for flutter (e.g., inferring execution type by checking the transitive closure of reachabe sources for `dart:flutter`) but can be more generallly useful for other client-side smarts (e.g., "is this a web entry point?" or "is this a test?").

More context here: https://github.com/dart-lang/sdk/issues/24893

R=brianwilkerson@google.com, devoncarew@google.com

Review URL: https://codereview.chromium.org/1491013002 .
This commit is contained in:
pq 2015-12-02 13:26:35 -08:00
parent daf5f6d258
commit 43f027fc05
15 changed files with 568 additions and 7 deletions

File diff suppressed because one or more lines are too long

View file

@ -938,6 +938,176 @@ class AnalysisGetHoverResult implements HasToJson {
return JenkinsSmiHash.finish(hash); return JenkinsSmiHash.finish(hash);
} }
} }
/**
* analysis.getReachableSources params
*
* {
* "file": FilePath
* }
*
* Clients may not extend, implement or mix-in this class.
*/
class AnalysisGetReachableSourcesParams implements HasToJson {
String _file;
/**
* The file for which reachable source information is being requested.
*/
String get file => _file;
/**
* The file for which reachable source information is being requested.
*/
void set file(String value) {
assert(value != null);
this._file = value;
}
AnalysisGetReachableSourcesParams(String file) {
this.file = file;
}
factory AnalysisGetReachableSourcesParams.fromJson(JsonDecoder jsonDecoder, String jsonPath, Object json) {
if (json == null) {
json = {};
}
if (json is Map) {
String file;
if (json.containsKey("file")) {
file = jsonDecoder.decodeString(jsonPath + ".file", json["file"]);
} else {
throw jsonDecoder.missingKey(jsonPath, "file");
}
return new AnalysisGetReachableSourcesParams(file);
} else {
throw jsonDecoder.mismatch(jsonPath, "analysis.getReachableSources params", json);
}
}
factory AnalysisGetReachableSourcesParams.fromRequest(Request request) {
return new AnalysisGetReachableSourcesParams.fromJson(
new RequestDecoder(request), "params", request._params);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> result = {};
result["file"] = file;
return result;
}
Request toRequest(String id) {
return new Request(id, "analysis.getReachableSources", toJson());
}
@override
String toString() => JSON.encode(toJson());
@override
bool operator==(other) {
if (other is AnalysisGetReachableSourcesParams) {
return file == other.file;
}
return false;
}
@override
int get hashCode {
int hash = 0;
hash = JenkinsSmiHash.combine(hash, file.hashCode);
return JenkinsSmiHash.finish(hash);
}
}
/**
* analysis.getReachableSources result
*
* {
* "sources": Map<String, List<String>>
* }
*
* Clients may not extend, implement or mix-in this class.
*/
class AnalysisGetReachableSourcesResult implements HasToJson {
Map<String, List<String>> _sources;
/**
* A mapping from source URIs to directly reachable source URIs. For example,
* a file "foo.dart" that imports "bar.dart" would have the corresponding
* mapping { "file:///foo.dart" : ["file:///bar.dart"] }. If "bar.dart" has
* further imports (or exports) there will be a mapping from the URI
* "file:///bar.dart" to them. To check if a specific URI is reachable from a
* given file, clients can check for its presence in the resulting key set.
*/
Map<String, List<String>> get sources => _sources;
/**
* A mapping from source URIs to directly reachable source URIs. For example,
* a file "foo.dart" that imports "bar.dart" would have the corresponding
* mapping { "file:///foo.dart" : ["file:///bar.dart"] }. If "bar.dart" has
* further imports (or exports) there will be a mapping from the URI
* "file:///bar.dart" to them. To check if a specific URI is reachable from a
* given file, clients can check for its presence in the resulting key set.
*/
void set sources(Map<String, List<String>> value) {
assert(value != null);
this._sources = value;
}
AnalysisGetReachableSourcesResult(Map<String, List<String>> sources) {
this.sources = sources;
}
factory AnalysisGetReachableSourcesResult.fromJson(JsonDecoder jsonDecoder, String jsonPath, Object json) {
if (json == null) {
json = {};
}
if (json is Map) {
Map<String, List<String>> sources;
if (json.containsKey("sources")) {
sources = jsonDecoder.decodeMap(jsonPath + ".sources", json["sources"], valueDecoder: (String jsonPath, Object json) => jsonDecoder.decodeList(jsonPath, json, jsonDecoder.decodeString));
} else {
throw jsonDecoder.missingKey(jsonPath, "sources");
}
return new AnalysisGetReachableSourcesResult(sources);
} else {
throw jsonDecoder.mismatch(jsonPath, "analysis.getReachableSources result", json);
}
}
factory AnalysisGetReachableSourcesResult.fromResponse(Response response) {
return new AnalysisGetReachableSourcesResult.fromJson(
new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id)), "result", response._result);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> result = {};
result["sources"] = sources;
return result;
}
Response toResponse(String id) {
return new Response(id, result: toJson());
}
@override
String toString() => JSON.encode(toJson());
@override
bool operator==(other) {
if (other is AnalysisGetReachableSourcesResult) {
return mapEqual(sources, other.sources, (List<String> a, List<String> b) => listEqual(a, b, (String a, String b) => a == b));
}
return false;
}
@override
int get hashCode {
int hash = 0;
hash = JenkinsSmiHash.combine(hash, sources.hashCode);
return JenkinsSmiHash.finish(hash);
}
}
/** /**
* analysis.getLibraryDependencies params * analysis.getLibraryDependencies params
* *
@ -13920,6 +14090,7 @@ class RequestError implements HasToJson {
* FORMAT_WITH_ERRORS * FORMAT_WITH_ERRORS
* GET_ERRORS_INVALID_FILE * GET_ERRORS_INVALID_FILE
* GET_NAVIGATION_INVALID_FILE * GET_NAVIGATION_INVALID_FILE
* GET_REACHABLE_SOURCES_INVALID_FILE
* INVALID_ANALYSIS_ROOT * INVALID_ANALYSIS_ROOT
* INVALID_EXECUTION_CONTEXT * INVALID_EXECUTION_CONTEXT
* INVALID_OVERLAY_CHANGE * INVALID_OVERLAY_CHANGE
@ -13977,6 +14148,12 @@ class RequestErrorCode implements Enum {
*/ */
static const GET_NAVIGATION_INVALID_FILE = const RequestErrorCode._("GET_NAVIGATION_INVALID_FILE"); static const GET_NAVIGATION_INVALID_FILE = const RequestErrorCode._("GET_NAVIGATION_INVALID_FILE");
/**
* An "analysis.getReachableSources" request specified a FilePath which does
* not match a file currently subject to analysis.
*/
static const GET_REACHABLE_SOURCES_INVALID_FILE = const RequestErrorCode._("GET_REACHABLE_SOURCES_INVALID_FILE");
/** /**
* A path passed as an argument to a request (such as analysis.reanalyze) is * A path passed as an argument to a request (such as analysis.reanalyze) is
* required to be an analysis root, but isn't. * required to be an analysis root, but isn't.
@ -14083,7 +14260,7 @@ class RequestErrorCode implements Enum {
/** /**
* A list containing all of the enum values that are defined. * A list containing all of the enum values that are defined.
*/ */
static const List<RequestErrorCode> VALUES = const <RequestErrorCode>[CONTENT_MODIFIED, FILE_NOT_ANALYZED, FORMAT_INVALID_FILE, FORMAT_WITH_ERRORS, GET_ERRORS_INVALID_FILE, GET_NAVIGATION_INVALID_FILE, INVALID_ANALYSIS_ROOT, INVALID_EXECUTION_CONTEXT, INVALID_OVERLAY_CHANGE, INVALID_PARAMETER, INVALID_REQUEST, NO_INDEX_GENERATED, ORGANIZE_DIRECTIVES_ERROR, REFACTORING_REQUEST_CANCELLED, SERVER_ALREADY_STARTED, SERVER_ERROR, SORT_MEMBERS_INVALID_FILE, SORT_MEMBERS_PARSE_ERRORS, UNANALYZED_PRIORITY_FILES, UNKNOWN_REQUEST, UNKNOWN_SOURCE, UNSUPPORTED_FEATURE]; static const List<RequestErrorCode> VALUES = const <RequestErrorCode>[CONTENT_MODIFIED, FILE_NOT_ANALYZED, FORMAT_INVALID_FILE, FORMAT_WITH_ERRORS, GET_ERRORS_INVALID_FILE, GET_NAVIGATION_INVALID_FILE, GET_REACHABLE_SOURCES_INVALID_FILE, INVALID_ANALYSIS_ROOT, INVALID_EXECUTION_CONTEXT, INVALID_OVERLAY_CHANGE, INVALID_PARAMETER, INVALID_REQUEST, NO_INDEX_GENERATED, ORGANIZE_DIRECTIVES_ERROR, REFACTORING_REQUEST_CANCELLED, SERVER_ALREADY_STARTED, SERVER_ERROR, SORT_MEMBERS_INVALID_FILE, SORT_MEMBERS_PARSE_ERRORS, UNANALYZED_PRIORITY_FILES, UNKNOWN_REQUEST, UNKNOWN_SOURCE, UNSUPPORTED_FEATURE];
final String name; final String name;
@ -14103,6 +14280,8 @@ class RequestErrorCode implements Enum {
return GET_ERRORS_INVALID_FILE; return GET_ERRORS_INVALID_FILE;
case "GET_NAVIGATION_INVALID_FILE": case "GET_NAVIGATION_INVALID_FILE":
return GET_NAVIGATION_INVALID_FILE; return GET_NAVIGATION_INVALID_FILE;
case "GET_REACHABLE_SOURCES_INVALID_FILE":
return GET_REACHABLE_SOURCES_INVALID_FILE;
case "INVALID_ANALYSIS_ROOT": case "INVALID_ANALYSIS_ROOT":
return INVALID_ANALYSIS_ROOT; return INVALID_ANALYSIS_ROOT;
case "INVALID_EXECUTION_CONTEXT": case "INVALID_EXECUTION_CONTEXT":

View file

@ -410,6 +410,16 @@ class Response {
RequestErrorCode.GET_NAVIGATION_INVALID_FILE, RequestErrorCode.GET_NAVIGATION_INVALID_FILE,
'Error during `analysis.getNavigation`: invalid file.')); 'Error during `analysis.getNavigation`: invalid file.'));
/**
* Initialize a newly created instance to represent the
* GET_REACHABLE_SOURCES_INVALID_FILE error condition.
*/
Response.getReachableSourcesInvalidFile(Request request)
: this(request.id,
error: new RequestError(
RequestErrorCode.GET_REACHABLE_SOURCES_INVALID_FILE,
'Error during `analysis.getReachableSources`: invalid file.'));
/** /**
* Initialize a newly created instance to represent an error condition caused * Initialize a newly created instance to represent an error condition caused
* by an analysis.reanalyze [request] that specifies an analysis root that is * by an analysis.reanalyze [request] that specifies an analysis root that is

View file

@ -26,6 +26,7 @@ const String ANALYSIS_GET_HOVER = 'analysis.getHover';
const String ANALYSIS_GET_LIBRARY_DEPENDENCIES = const String ANALYSIS_GET_LIBRARY_DEPENDENCIES =
'analysis.getLibraryDependencies'; 'analysis.getLibraryDependencies';
const String ANALYSIS_GET_NAVIGATION = 'analysis.getNavigation'; const String ANALYSIS_GET_NAVIGATION = 'analysis.getNavigation';
const String ANALYSIS_GET_REACHABLE_SOURCES = 'analysis.getReachableSources';
const String ANALYSIS_REANALYZE = 'analysis.reanalyze'; const String ANALYSIS_REANALYZE = 'analysis.reanalyze';
const String ANALYSIS_SET_ANALYSIS_ROOTS = 'analysis.setAnalysisRoots'; const String ANALYSIS_SET_ANALYSIS_ROOTS = 'analysis.setAnalysisRoots';
const String ANALYSIS_SET_GENERAL_SUBSCRIPTIONS = const String ANALYSIS_SET_GENERAL_SUBSCRIPTIONS =

View file

@ -21,6 +21,7 @@ import 'package:analysis_server/src/operation/operation_analysis.dart'
import 'package:analysis_server/src/protocol/protocol_internal.dart'; import 'package:analysis_server/src/protocol/protocol_internal.dart';
import 'package:analysis_server/src/protocol_server.dart'; import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/dependencies/library_dependencies.dart'; import 'package:analysis_server/src/services/dependencies/library_dependencies.dart';
import 'package:analysis_server/src/services/dependencies/reachable_source_collector.dart';
import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart'; import 'package:analyzer/src/generated/element.dart';
@ -164,6 +165,22 @@ class AnalysisDomainHandler implements RequestHandler {
return Response.DELAYED_RESPONSE; return Response.DELAYED_RESPONSE;
} }
/**
* Implement the `analysis.getReachableSources` request.
*/
Response getReachableSources(Request request) {
AnalysisGetReachableSourcesParams params =
new AnalysisGetReachableSourcesParams.fromRequest(request);
ContextSourcePair pair = server.getContextSourcePair(params.file);
if (pair.context == null || pair.source == null) {
return new Response.getReachableSourcesInvalidFile(request);
}
Map<String, List<String>> sources = new ReachableSourceCollector(
pair.source, pair.context).collectSources();
return new AnalysisGetReachableSourcesResult(sources)
.toResponse(request.id);
}
@override @override
Response handleRequest(Request request) { Response handleRequest(Request request) {
try { try {
@ -176,6 +193,8 @@ class AnalysisDomainHandler implements RequestHandler {
return getLibraryDependencies(request); return getLibraryDependencies(request);
} else if (requestName == ANALYSIS_GET_NAVIGATION) { } else if (requestName == ANALYSIS_GET_NAVIGATION) {
return getNavigation(request); return getNavigation(request);
} else if (requestName == ANALYSIS_GET_REACHABLE_SOURCES) {
return getReachableSources(request);
} else if (requestName == ANALYSIS_REANALYZE) { } else if (requestName == ANALYSIS_REANALYZE) {
return reanalyze(request); return reanalyze(request);
} else if (requestName == ANALYSIS_SET_ANALYSIS_ROOTS) { } else if (requestName == ANALYSIS_SET_ANALYSIS_ROOTS) {

View file

@ -0,0 +1,53 @@
// 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 services.dependencies.reachable_source_collector;
import 'dart:collection';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/task/dart.dart';
/// Collects reachable sources.
class ReachableSourceCollector {
final Map<String, List<String>> _sourceMap =
new HashMap<String, List<String>>();
final Source source;
final AnalysisContext context;
ReachableSourceCollector(this.source, this.context) {
if (source == null) {
throw new ArgumentError.notNull('source');
}
if (context == null) {
throw new ArgumentError.notNull('context');
}
}
/// Collect reachable sources.
Map<String, List<String>> collectSources() {
_addDependencies(source);
return _sourceMap;
}
void _addDependencies(Source source) {
String sourceUri = source.uri.toString();
// Careful not to revisit.
if (_sourceMap[source.uri.toString()] != null) {
return;
}
List<Source> sources = <Source>[];
sources.addAll(context.computeResult(source, IMPORTED_LIBRARIES));
sources.addAll(context.computeResult(source, EXPORTED_LIBRARIES));
_sourceMap[sourceUri] =
sources.map((source) => source.uri.toString()).toList();
sources.forEach((s) => _addDependencies(s));
}
}

View file

@ -55,6 +55,46 @@ main() {
group('updateContent', testUpdateContent); group('updateContent', testUpdateContent);
group('AnalysisDomainHandler', () { group('AnalysisDomainHandler', () {
group('getReachableSources', () {
test('valid sources', () async {
String fileA = '/project/a.dart';
String fileB = '/project/b.dart';
resourceProvider.newFile(fileA, 'import "b.dart";');
resourceProvider.newFile(fileB, '');
server.setAnalysisRoots('0', ['/project/'], [], {});
await server.onAnalysisComplete;
var request =
new AnalysisGetReachableSourcesParams(fileA).toRequest('0');
var response = handler.handleRequest(request);
var json = response.toJson()[Response.RESULT];
// Sanity checks.
expect(json['sources'], hasLength(6));
expect(json['sources']['file:///project/a.dart'],
unorderedEquals(['dart:core', 'file:///project/b.dart']));
expect(json['sources']['file:///project/b.dart'], ['dart:core']);
});
test('invalid source', () async {
resourceProvider.newFile('/project/a.dart', 'import "b.dart";');
server.setAnalysisRoots('0', ['/project/'], [], {});
await server.onAnalysisComplete;
var request =
new AnalysisGetReachableSourcesParams('/does/not/exist.dart')
.toRequest('0');
var response = handler.handleRequest(request);
expect(response.error, isNotNull);
expect(response.error.code,
RequestErrorCode.GET_REACHABLE_SOURCES_INVALID_FILE);
});
});
group('setAnalysisRoots', () { group('setAnalysisRoots', () {
Response testSetAnalysisRoots( Response testSetAnalysisRoots(
List<String> included, List<String> excluded) { List<String> included, List<String> excluded) {

View file

@ -237,6 +237,41 @@ abstract class IntegrationTestMixin {
}); });
} }
/**
* Return the transitive closure of reachable sources for a given file.
*
* If a request is made for a file which does not exist, or which is not
* currently subject to analysis (e.g. because it is not associated with any
* analysis root specified to analysis.setAnalysisRoots), an error of type
* GET_REACHABLE_SOURCES_INVALID_FILE will be generated.
*
* Parameters
*
* file ( FilePath )
*
* The file for which reachable source information is being requested.
*
* Returns
*
* sources ( Map<String, List<String>> )
*
* A mapping from source URIs to directly reachable source URIs. For
* example, a file "foo.dart" that imports "bar.dart" would have the
* corresponding mapping { "file:///foo.dart" : ["file:///bar.dart"] }. If
* "bar.dart" has further imports (or exports) there will be a mapping from
* the URI "file:///bar.dart" to them. To check if a specific URI is
* reachable from a given file, clients can check for its presence in the
* resulting key set.
*/
Future<AnalysisGetReachableSourcesResult> sendAnalysisGetReachableSources(String file) {
var params = new AnalysisGetReachableSourcesParams(file).toJson();
return server.send("analysis.getReachableSources", params)
.then((result) {
ResponseDecoder decoder = new ResponseDecoder(null);
return new AnalysisGetReachableSourcesResult.fromJson(decoder, 'result', result);
});
}
/** /**
* Return library dependency information for use in client-side indexing and * Return library dependency information for use in client-side indexing and
* package URI resolution. * package URI resolution.

View file

@ -152,6 +152,30 @@ final Matcher isAnalysisGetHoverResult = new LazyMatcher(() => new MatchesJsonOb
"hovers": isListOf(isHoverInformation) "hovers": isListOf(isHoverInformation)
})); }));
/**
* analysis.getReachableSources params
*
* {
* "file": FilePath
* }
*/
final Matcher isAnalysisGetReachableSourcesParams = new LazyMatcher(() => new MatchesJsonObject(
"analysis.getReachableSources params", {
"file": isFilePath
}));
/**
* analysis.getReachableSources result
*
* {
* "sources": Map<String, List<String>>
* }
*/
final Matcher isAnalysisGetReachableSourcesResult = new LazyMatcher(() => new MatchesJsonObject(
"analysis.getReachableSources result", {
"sources": isMapOf(isString, isListOf(isString))
}));
/** /**
* analysis.getLibraryDependencies params * analysis.getLibraryDependencies params
*/ */
@ -2077,6 +2101,7 @@ final Matcher isRequestError = new LazyMatcher(() => new MatchesJsonObject(
* FORMAT_WITH_ERRORS * FORMAT_WITH_ERRORS
* GET_ERRORS_INVALID_FILE * GET_ERRORS_INVALID_FILE
* GET_NAVIGATION_INVALID_FILE * GET_NAVIGATION_INVALID_FILE
* GET_REACHABLE_SOURCES_INVALID_FILE
* INVALID_ANALYSIS_ROOT * INVALID_ANALYSIS_ROOT
* INVALID_EXECUTION_CONTEXT * INVALID_EXECUTION_CONTEXT
* INVALID_OVERLAY_CHANGE * INVALID_OVERLAY_CHANGE
@ -2102,6 +2127,7 @@ final Matcher isRequestErrorCode = new MatchesEnum("RequestErrorCode", [
"FORMAT_WITH_ERRORS", "FORMAT_WITH_ERRORS",
"GET_ERRORS_INVALID_FILE", "GET_ERRORS_INVALID_FILE",
"GET_NAVIGATION_INVALID_FILE", "GET_NAVIGATION_INVALID_FILE",
"GET_REACHABLE_SOURCES_INVALID_FILE",
"INVALID_ANALYSIS_ROOT", "INVALID_ANALYSIS_ROOT",
"INVALID_EXECUTION_CONTEXT", "INVALID_EXECUTION_CONTEXT",
"INVALID_OVERLAY_CHANGE", "INVALID_OVERLAY_CHANGE",

View file

@ -34,7 +34,7 @@ class LibraryDependenciesTest extends AbstractContextTest {
// Cycles // Cycles
expect(libs, contains('/lib1.dart')); expect(libs, contains('/lib1.dart'));
expect(libs, contains('/lib2.dart')); expect(libs, contains('/lib2.dart'));
// Regular sourcs // Regular sources
expect(libs, contains('/lib3.dart')); expect(libs, contains('/lib3.dart'));
expect(libs, contains('/lib4.dart')); expect(libs, contains('/lib4.dart'));
// Non-source, referenced by source // Non-source, referenced by source

View file

@ -0,0 +1,84 @@
// 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 test.services.dependencies.import_collector;
import 'package:analysis_server/src/services/dependencies/reachable_source_collector.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:unittest/unittest.dart';
import '../../abstract_context.dart';
import '../../utils.dart';
main() {
initializeTestEnvironment();
defineReflectiveTests(ReachableSourceCollectorTest);
}
@reflectiveTest
class ReachableSourceCollectorTest extends AbstractContextTest {
CompilationUnit addLibrary(String path, String contents) =>
resolveLibraryUnit(addSource(path, contents));
Map<String, List<String>> importsFor(Source source) =>
new ReachableSourceCollector(source, context).collectSources();
test_null_context() {
Source lib = addSource('/lib.dart', '');
expect(() => new ReachableSourceCollector(lib, null),
throwsA(new isInstanceOf<ArgumentError>()));
}
test_null_source() {
expect(() => new ReachableSourceCollector(null, context),
throwsA(new isInstanceOf<ArgumentError>()));
}
test_sources() {
Source lib1 = addSource(
'/lib1.dart',
'''
import "lib2.dart";
import "dart:html";''');
Source lib2 = addSource('/lib2.dart', 'import "lib1.dart";');
Source lib3 = addSource('/lib3.dart', 'import "lib4.dart";');
addSource('/lib4.dart', 'import "lib3.dart";');
Map<String, List<String>> imports = importsFor(lib1);
// Verify keys.
expect(
imports.keys,
unorderedEquals([
'dart:_internal',
'dart:async',
'dart:core',
'dart:html',
'dart:math',
'file:///lib1.dart',
'file:///lib2.dart',
]));
// Values.
expect(imports['file:///lib1.dart'],
unorderedEquals(['dart:core', 'dart:html', 'file:///lib2.dart']));
// Check transitivity.
expect(importsFor(lib2).keys, contains('dart:html'));
// Cycles should be OK.
expect(
importsFor(lib3).keys,
unorderedEquals([
'dart:_internal',
'dart:async',
'dart:core',
'dart:math',
'file:///lib3.dart',
'file:///lib4.dart'
]));
}
}

View file

@ -7,12 +7,14 @@ library test.services.dependencies;
import 'package:unittest/unittest.dart'; import 'package:unittest/unittest.dart';
import '../../utils.dart'; import '../../utils.dart';
import 'library_dependencies_test.dart' as library_dependencies_test; import 'library_dependencies_test.dart' as library_dependencies;
import 'reachable_source_collector_test.dart' as reachable_source_collector;
/// Utility for manually running all tests. /// Utility for manually running all tests.
main() { main() {
initializeTestEnvironment(); initializeTestEnvironment();
group('dependencies', () { group('dependencies', () {
library_dependencies_test.main(); library_dependencies.main();
reachable_source_collector.main();
}); });
} }

View file

@ -118,6 +118,20 @@ public interface AnalysisServer {
*/ */
public void analysis_getNavigation(String file, int offset, int length, GetNavigationConsumer consumer); public void analysis_getNavigation(String file, int offset, int length, GetNavigationConsumer consumer);
/**
* {@code analysis.getReachableSources}
*
* Return the transitive closure of reachable sources for a given file.
*
* If a request is made for a file which does not exist, or which is not currently subject to
* analysis (e.g. because it is not associated with any analysis root specified to
* analysis.setAnalysisRoots), an error of type GET_REACHABLE_SOURCES_INVALID_FILE will be
* generated.
*
* @param file The file for which reachable source information is being requested.
*/
public void analysis_getReachableSources(String file, GetReachableSourcesConsumer consumer);
/** /**
* {@code analysis.reanalyze} * {@code analysis.reanalyze}
* *

View file

@ -58,6 +58,12 @@ public class RequestErrorCode {
*/ */
public static final String GET_NAVIGATION_INVALID_FILE = "GET_NAVIGATION_INVALID_FILE"; public static final String GET_NAVIGATION_INVALID_FILE = "GET_NAVIGATION_INVALID_FILE";
/**
* An "analysis.getReachableSources" request specified a FilePath which does not match a file
* currently subject to analysis.
*/
public static final String GET_REACHABLE_SOURCES_INVALID_FILE = "GET_REACHABLE_SOURCES_INVALID_FILE";
/** /**
* A path passed as an argument to a request (such as analysis.reanalyze) is required to be an * A path passed as an argument to a request (such as analysis.reanalyze) is required to be an
* analysis root, but isn't. * analysis root, but isn't.

View file

@ -6,7 +6,7 @@
</head> </head>
<body> <body>
<h1>Analysis Server API Specification</h1> <h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version <version>1.12.0</version></h1> <h1 style="color:#999999">Version <version>1.13.0</version></h1>
<p> <p>
This document contains a specification of the API provided by the This document contains a specification of the API provided by the
analysis server. The API in this document is currently under analysis server. The API in this document is currently under
@ -395,6 +395,42 @@
</field> </field>
</result> </result>
</request> </request>
<request method="getReachableSources">
<p>
Return the transitive closure of reachable sources for a given file.
</p>
<p>
If a request is made for a file which does not exist, or
which is not currently subject to analysis (e.g. because it
is not associated with any analysis root specified to
analysis.setAnalysisRoots), an error of type
<tt>GET_REACHABLE_SOURCES_INVALID_FILE</tt> will be generated.
</p>
<params>
<field name="file">
<ref>FilePath</ref>
<p>
The file for which reachable source information is being requested.
</p>
</field>
</params>
<result>
<field name="sources">
<map>
<key><ref>String</ref></key>
<value><list>><ref>String</ref></list></value>
</map>
<p>
A mapping from source URIs to directly reachable source URIs. For example,
a file "foo.dart" that imports "bar.dart" would have the corresponding mapping
{ "file:///foo.dart" : ["file:///bar.dart"] }. If "bar.dart" has further imports
(or exports) there will be a mapping from the URI "file:///bar.dart" to them.
To check if a specific URI is reachable from a given file, clients can check
for its presence in the resulting key set.
</p>
</field>
</result>
</request>
<request method="getLibraryDependencies"> <request method="getLibraryDependencies">
<p> <p>
Return library dependency information for use in client-side indexing Return library dependency information for use in client-side indexing
@ -3649,6 +3685,14 @@
analysis. analysis.
</p> </p>
</value> </value>
<value>
<code>GET_REACHABLE_SOURCES_INVALID_FILE</code>
<p>
An "analysis.getReachableSources" request specified a FilePath
which does not match a file currently subject to
analysis.
</p>
</value>
<value> <value>
<code>INVALID_ANALYSIS_ROOT</code> <code>INVALID_ANALYSIS_ROOT</code>
<p> <p>