mirror of
https://github.com/dart-lang/sdk
synced 2024-09-22 02:28:56 +00:00
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:
parent
daf5f6d258
commit
43f027fc05
File diff suppressed because one or more lines are too long
|
@ -938,6 +938,176 @@ class AnalysisGetHoverResult implements HasToJson {
|
|||
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
|
||||
*
|
||||
|
@ -13920,6 +14090,7 @@ class RequestError implements HasToJson {
|
|||
* 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
|
||||
|
@ -13977,6 +14148,12 @@ class RequestErrorCode implements Enum {
|
|||
*/
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
|
||||
|
@ -14103,6 +14280,8 @@ class RequestErrorCode implements Enum {
|
|||
return GET_ERRORS_INVALID_FILE;
|
||||
case "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":
|
||||
return INVALID_ANALYSIS_ROOT;
|
||||
case "INVALID_EXECUTION_CONTEXT":
|
||||
|
|
|
@ -410,6 +410,16 @@ class Response {
|
|||
RequestErrorCode.GET_NAVIGATION_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
|
||||
* by an analysis.reanalyze [request] that specifies an analysis root that is
|
||||
|
|
|
@ -26,6 +26,7 @@ const String ANALYSIS_GET_HOVER = 'analysis.getHover';
|
|||
const String ANALYSIS_GET_LIBRARY_DEPENDENCIES =
|
||||
'analysis.getLibraryDependencies';
|
||||
const String ANALYSIS_GET_NAVIGATION = 'analysis.getNavigation';
|
||||
const String ANALYSIS_GET_REACHABLE_SOURCES = 'analysis.getReachableSources';
|
||||
const String ANALYSIS_REANALYZE = 'analysis.reanalyze';
|
||||
const String ANALYSIS_SET_ANALYSIS_ROOTS = 'analysis.setAnalysisRoots';
|
||||
const String ANALYSIS_SET_GENERAL_SUBSCRIPTIONS =
|
||||
|
|
|
@ -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_server.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/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -164,6 +165,22 @@ class AnalysisDomainHandler implements RequestHandler {
|
|||
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
|
||||
Response handleRequest(Request request) {
|
||||
try {
|
||||
|
@ -176,6 +193,8 @@ class AnalysisDomainHandler implements RequestHandler {
|
|||
return getLibraryDependencies(request);
|
||||
} else if (requestName == ANALYSIS_GET_NAVIGATION) {
|
||||
return getNavigation(request);
|
||||
} else if (requestName == ANALYSIS_GET_REACHABLE_SOURCES) {
|
||||
return getReachableSources(request);
|
||||
} else if (requestName == ANALYSIS_REANALYZE) {
|
||||
return reanalyze(request);
|
||||
} else if (requestName == ANALYSIS_SET_ANALYSIS_ROOTS) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -55,6 +55,46 @@ main() {
|
|||
group('updateContent', testUpdateContent);
|
||||
|
||||
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', () {
|
||||
Response testSetAnalysisRoots(
|
||||
List<String> included, List<String> excluded) {
|
||||
|
|
|
@ -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
|
||||
* package URI resolution.
|
||||
|
|
|
@ -152,6 +152,30 @@ final Matcher isAnalysisGetHoverResult = new LazyMatcher(() => new MatchesJsonOb
|
|||
"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
|
||||
*/
|
||||
|
@ -2077,6 +2101,7 @@ final Matcher isRequestError = new LazyMatcher(() => new MatchesJsonObject(
|
|||
* 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
|
||||
|
@ -2102,6 +2127,7 @@ final Matcher isRequestErrorCode = new MatchesEnum("RequestErrorCode", [
|
|||
"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",
|
||||
|
|
|
@ -34,7 +34,7 @@ class LibraryDependenciesTest extends AbstractContextTest {
|
|||
// Cycles
|
||||
expect(libs, contains('/lib1.dart'));
|
||||
expect(libs, contains('/lib2.dart'));
|
||||
// Regular sourcs
|
||||
// Regular sources
|
||||
expect(libs, contains('/lib3.dart'));
|
||||
expect(libs, contains('/lib4.dart'));
|
||||
// Non-source, referenced by source
|
||||
|
|
|
@ -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'
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -7,12 +7,14 @@ library test.services.dependencies;
|
|||
import 'package:unittest/unittest.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.
|
||||
main() {
|
||||
initializeTestEnvironment();
|
||||
group('dependencies', () {
|
||||
library_dependencies_test.main();
|
||||
library_dependencies.main();
|
||||
reachable_source_collector.main();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -118,6 +118,20 @@ public interface AnalysisServer {
|
|||
*/
|
||||
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}
|
||||
*
|
||||
|
|
|
@ -58,6 +58,12 @@ public class RequestErrorCode {
|
|||
*/
|
||||
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
|
||||
* analysis root, but isn't.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<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>
|
||||
This document contains a specification of the API provided by the
|
||||
analysis server. The API in this document is currently under
|
||||
|
@ -395,6 +395,42 @@
|
|||
</field>
|
||||
</result>
|
||||
</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">
|
||||
<p>
|
||||
Return library dependency information for use in client-side indexing
|
||||
|
@ -3649,6 +3685,14 @@
|
|||
analysis.
|
||||
</p>
|
||||
</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>
|
||||
<code>INVALID_ANALYSIS_ROOT</code>
|
||||
<p>
|
||||
|
|
Loading…
Reference in a new issue