mirror of
https://github.com/dart-lang/sdk
synced 2024-09-22 06:31:19 +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);
|
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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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('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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 '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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue