blob: 3744121d02f9bd51bfaa4204e18803dc9afee74d [file] [log] [blame]
import 'dart:io' as io;
import 'dart:math' show min;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/cache.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/resynthesize.dart';
import 'package:analyzer/src/task/api/dart.dart';
import 'package:analyzer/src/task/api/general.dart';
import 'package:analyzer/src/task/api/model.dart';
import 'package:analyzer/src/task/dart.dart';
import 'package:front_end/src/base/source.dart';
/**
* A [ConflictingSummaryException] indicates that two different summaries
* provided to a [SummaryDataStore] conflict.
*/
class ConflictingSummaryException implements Exception {
final String duplicatedUri;
final String summary1Uri;
final String summary2Uri;
String _message;
ConflictingSummaryException(Iterable<String> summaryPaths, this.duplicatedUri,
this.summary1Uri, this.summary2Uri) {
// Paths are often quite long. Find and extract out a common prefix to
// build a more readable error message.
var prefix = _commonPrefix(summaryPaths.toList());
_message = '''
These summaries conflict because they overlap:
- ${summary1Uri.substring(prefix)}
- ${summary2Uri.substring(prefix)}
Both contain the file: $duplicatedUri.
This typically indicates an invalid build rule where two or more targets
include the same source.
''';
}
String toString() => _message;
/// Given a set of file paths, find a common prefix.
int _commonPrefix(List<String> strings) {
if (strings.isEmpty) return 0;
var first = strings.first;
int common = first.length;
for (int i = 1; i < strings.length; ++i) {
var current = strings[i];
common = min(common, current.length);
for (int j = 0; j < common; ++j) {
if (first[j] != current[j]) {
common = j;
if (common == 0) return 0;
break;
}
}
}
// The prefix should end with a file separator.
var last =
first.substring(0, common).lastIndexOf(io.Platform.pathSeparator);
return last < 0 ? 0 : last + 1;
}
}
/**
* The [ResultProvider] that provides results from input package summaries.
*/
class InputPackagesResultProvider extends ResynthesizerResultProvider {
InputPackagesResultProvider(
InternalAnalysisContext context, SummaryDataStore dataStore)
: super(context, dataStore) {
createResynthesizer();
context.typeProvider = resynthesizer.typeProvider;
resynthesizer.finishCoreAsyncLibraries();
}
@override
bool hasResultsForSource(Source source) {
String uriString = source.uri.toString();
return resynthesizer.hasLibrarySummary(uriString);
}
}
/**
* The [UriResolver] that knows about sources that are served from their
* summaries.
*/
@deprecated
class InSummaryPackageUriResolver extends UriResolver {
final SummaryDataStore _dataStore;
InSummaryPackageUriResolver(this._dataStore);
@override
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
actualUri ??= uri;
String uriString = uri.toString();
UnlinkedUnit unit = _dataStore.unlinkedMap[uriString];
if (unit != null) {
String summaryPath = _dataStore.uriToSummaryPath[uriString];
return new InSummarySource(actualUri, summaryPath);
}
return null;
}
}
/**
* A placeholder of a source that is part of a package whose analysis results
* are served from its summary. This source uses its URI as [fullName] and has
* empty contents.
*/
class InSummarySource extends BasicSource {
/**
* The summary file where this source was defined.
*/
final String summaryPath;
InSummarySource(Uri uri, this.summaryPath) : super(uri);
@override
TimestampedData<String> get contents => new TimestampedData<String>(0, '');
@override
int get modificationStamp => 0;
@override
UriKind get uriKind => UriKind.PACKAGE_URI;
@override
bool exists() => true;
@override
String toString() => uri.toString();
}
/**
* The [UriResolver] that knows about sources that are served from their
* summaries.
*/
class InSummaryUriResolver extends UriResolver {
ResourceProvider resourceProvider;
final SummaryDataStore _dataStore;
InSummaryUriResolver(this.resourceProvider, this._dataStore);
@override
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
actualUri ??= uri;
String uriString = uri.toString();
UnlinkedUnit unit = _dataStore.unlinkedMap[uriString];
if (unit != null) {
String summaryPath = _dataStore.uriToSummaryPath[uriString];
return new InSummarySource(actualUri, summaryPath);
}
return null;
}
}
/**
* The [ResultProvider] that provides results using summary resynthesizer.
*/
abstract class ResynthesizerResultProvider extends ResultProvider {
final InternalAnalysisContext context;
final SummaryDataStore _dataStore;
StoreBasedSummaryResynthesizer _resynthesizer;
ResynthesizerResultProvider(this.context, this._dataStore);
SummaryResynthesizer get resynthesizer => _resynthesizer;
/**
* Add a new [bundle] to the resynthesizer.
*/
void addBundle(String path, PackageBundle bundle) {
_dataStore.addBundle(path, bundle);
}
@override
bool compute(CacheEntry entry, ResultDescriptor result) {
AnalysisTarget target = entry.target;
if (result == TYPE_PROVIDER) {
entry.setValue(result as ResultDescriptor<TypeProvider>,
_resynthesizer.typeProvider, TargetedResult.EMPTY_LIST);
return true;
}
// LINE_INFO can be provided using just the UnlinkedUnit.
if (target is Source && result == LINE_INFO) {
String uriString = target.uri.toString();
UnlinkedUnit unlinkedUnit = _dataStore.unlinkedMap[uriString];
if (unlinkedUnit != null) {
List<int> lineStarts = unlinkedUnit.lineStarts;
if (lineStarts.isNotEmpty) {
LineInfo lineInfo = new LineInfo(lineStarts);
entry.setValue(result as ResultDescriptor<LineInfo>, lineInfo,
TargetedResult.EMPTY_LIST);
return true;
}
}
return false;
}
// Check whether there are results for the source.
if (!hasResultsForSource(target.librarySource ?? target.source)) {
return false;
}
// Constant expressions are always resolved in summaries.
if (result == CONSTANT_EXPRESSION_RESOLVED &&
target is ConstantEvaluationTarget) {
entry.setValue(
result as ResultDescriptor<bool>, true, TargetedResult.EMPTY_LIST);
return true;
}
// Provide results for Source.
if (target is Source) {
String uriString = target.uri.toString();
// Provide known results.
if (result == LIBRARY_ELEMENT1 ||
result == LIBRARY_ELEMENT2 ||
result == LIBRARY_ELEMENT3 ||
result == LIBRARY_ELEMENT4 ||
result == LIBRARY_ELEMENT5 ||
result == LIBRARY_ELEMENT6 ||
result == LIBRARY_ELEMENT7 ||
result == LIBRARY_ELEMENT8 ||
result == LIBRARY_ELEMENT9 ||
result == LIBRARY_ELEMENT) {
LibraryElement libraryElement =
resynthesizer.getLibraryElement(uriString);
entry.setValue(result as ResultDescriptor<LibraryElement>,
libraryElement, TargetedResult.EMPTY_LIST);
return true;
} else if (result == READY_LIBRARY_ELEMENT2 ||
result == READY_LIBRARY_ELEMENT6 ||
result == READY_LIBRARY_ELEMENT7) {
entry.setValue(
result as ResultDescriptor<bool>, true, TargetedResult.EMPTY_LIST);
return true;
} else if (result == MODIFICATION_TIME) {
entry.setValue(
result as ResultDescriptor<int>, 0, TargetedResult.EMPTY_LIST);
return true;
} else if (result == SOURCE_KIND) {
UnlinkedUnit unlinked = _dataStore.unlinkedMap[uriString];
if (unlinked != null) {
entry.setValue(
result as ResultDescriptor<SourceKind>,
unlinked.isPartOf ? SourceKind.PART : SourceKind.LIBRARY,
TargetedResult.EMPTY_LIST);
return true;
}
return false;
} else if (result == CONTAINING_LIBRARIES) {
List<String> libraryUriStrings =
_dataStore.getContainingLibraryUris(uriString);
if (libraryUriStrings != null) {
List<Source> librarySources = libraryUriStrings
.map((libraryUriString) =>
context.sourceFactory.resolveUri(target, libraryUriString))
.toList(growable: false);
entry.setValue(result as ResultDescriptor<List<Source>>,
librarySources, TargetedResult.EMPTY_LIST);
return true;
}
return false;
}
} else if (target is LibrarySpecificUnit) {
if (result == CREATED_RESOLVED_UNIT1 ||
result == CREATED_RESOLVED_UNIT2 ||
result == CREATED_RESOLVED_UNIT3 ||
result == CREATED_RESOLVED_UNIT4 ||
result == CREATED_RESOLVED_UNIT5 ||
result == CREATED_RESOLVED_UNIT6 ||
result == CREATED_RESOLVED_UNIT7 ||
result == CREATED_RESOLVED_UNIT8 ||
result == CREATED_RESOLVED_UNIT9 ||
result == CREATED_RESOLVED_UNIT10 ||
result == CREATED_RESOLVED_UNIT11) {
entry.setValue(
result as ResultDescriptor<bool>, true, TargetedResult.EMPTY_LIST);
return true;
}
if (result == COMPILATION_UNIT_ELEMENT) {
String libraryUri = target.library.uri.toString();
String unitUri = target.unit.uri.toString();
CompilationUnitElement unit = resynthesizer.getElement(
new ElementLocationImpl.con3(<String>[libraryUri, unitUri]));
if (unit != null) {
entry.setValue(result as ResultDescriptor<CompilationUnitElement>,
unit, TargetedResult.EMPTY_LIST);
return true;
}
}
} else if (target is VariableElement) {
if (result == INFERRED_STATIC_VARIABLE) {
entry.setValue(result as ResultDescriptor<VariableElement>, target,
TargetedResult.EMPTY_LIST);
return true;
}
}
// Unknown target.
return false;
}
/**
* Create the [resynthesizer] instance.
*
* Subclasses must call this method in their constructors.
*/
void createResynthesizer() {
_resynthesizer = new StoreBasedSummaryResynthesizer(context,
context.sourceFactory, context.analysisOptions.strongMode, _dataStore);
}
/**
* Return `true` if this result provider can provide a result for the
* given [source]. The provider must ensure that [addBundle] is invoked for
* every bundle that would be required to provide results for the [source].
*/
bool hasResultsForSource(Source source);
}
/**
* A concrete resynthesizer that serves summaries from [SummaryDataStore].
*/
class StoreBasedSummaryResynthesizer extends SummaryResynthesizer {
final SummaryDataStore _dataStore;
StoreBasedSummaryResynthesizer(AnalysisContext context,
SourceFactory sourceFactory, bool strongMode, this._dataStore)
: super(context, sourceFactory, strongMode);
@override
LinkedLibrary getLinkedSummary(String uri) {
return _dataStore.linkedMap[uri];
}
@override
UnlinkedUnit getUnlinkedSummary(String uri) {
return _dataStore.unlinkedMap[uri];
}
@override
bool hasLibrarySummary(String uri) {
LinkedLibrary linkedLibrary = _dataStore.linkedMap[uri];
return linkedLibrary != null;
}
}
/**
* A [SummaryDataStore] is a container for the data extracted from a set of
* summary package bundles. It contains maps which can be used to find linked
* and unlinked summaries by URI.
*/
class SummaryDataStore {
/**
* List of all [PackageBundle]s.
*/
final List<PackageBundle> bundles = <PackageBundle>[];
/**
* Map from the URI of a compilation unit to the unlinked summary of that
* compilation unit.
*/
final Map<String, UnlinkedUnit> unlinkedMap = <String, UnlinkedUnit>{};
/**
* Map from the URI of a library to the linked summary of that library.
*/
final Map<String, LinkedLibrary> linkedMap = <String, LinkedLibrary>{};
/**
* Map from the URI of a library to the summary path that contained it.
*/
final Map<String, String> uriToSummaryPath = <String, String>{};
/**
* List of summary paths.
*/
final Iterable<String> _summaryPaths;
/**
* If true, do not accept multiple summaries that contain the same Dart uri.
*/
bool _disallowOverlappingSummaries;
/**
* Create a [SummaryDataStore] and populate it with the summaries in
* [summaryPaths].
*/
SummaryDataStore(Iterable<String> summaryPaths,
{bool disallowOverlappingSummaries: false,
ResourceProvider resourceProvider})
: _summaryPaths = summaryPaths,
_disallowOverlappingSummaries = disallowOverlappingSummaries {
summaryPaths.forEach((String path) => _fillMaps(path, resourceProvider));
}
/**
* Add the given [bundle] loaded from the file with the given [path].
*/
void addBundle(String path, PackageBundle bundle) {
bundles.add(bundle);
for (int i = 0; i < bundle.unlinkedUnitUris.length; i++) {
String uri = bundle.unlinkedUnitUris[i];
if (_disallowOverlappingSummaries &&
uriToSummaryPath.containsKey(uri) &&
(uriToSummaryPath[uri] != path)) {
throw new ConflictingSummaryException(
_summaryPaths, uri, uriToSummaryPath[uri], path);
}
uriToSummaryPath[uri] = path;
addUnlinkedUnit(uri, bundle.unlinkedUnits[i]);
}
for (int i = 0; i < bundle.linkedLibraryUris.length; i++) {
String uri = bundle.linkedLibraryUris[i];
addLinkedLibrary(uri, bundle.linkedLibraries[i]);
}
}
/**
* Add the given [linkedLibrary] with the given [uri].
*/
void addLinkedLibrary(String uri, LinkedLibrary linkedLibrary) {
linkedMap[uri] = linkedLibrary;
}
/**
* Add into this store the unlinked units and linked libraries of [other].
*/
void addStore(SummaryDataStore other) {
unlinkedMap.addAll(other.unlinkedMap);
linkedMap.addAll(other.linkedMap);
}
/**
* Add the given [unlinkedUnit] with the given [uri].
*/
void addUnlinkedUnit(String uri, UnlinkedUnit unlinkedUnit) {
unlinkedMap[uri] = unlinkedUnit;
}
/**
* Return a list of absolute URIs of the libraries that contain the unit with
* the given [unitUriString], or `null` if no such library is in the store.
*/
List<String> getContainingLibraryUris(String unitUriString) {
// The unit is the defining unit of a library.
if (linkedMap.containsKey(unitUriString)) {
return <String>[unitUriString];
}
// Check every unlinked unit whether it uses [unitUri] as a part.
List<String> libraryUriStrings = <String>[];
unlinkedMap.forEach((unlinkedUnitUriString, unlinkedUnit) {
Uri libraryUri = Uri.parse(unlinkedUnitUriString);
for (String partUriString in unlinkedUnit.publicNamespace.parts) {
Uri partUri = Uri.parse(partUriString);
String partAbsoluteUriString =
resolveRelativeUri(libraryUri, partUri).toString();
if (partAbsoluteUriString == unitUriString) {
libraryUriStrings.add(unlinkedUnitUriString);
}
}
});
return libraryUriStrings.isNotEmpty ? libraryUriStrings : null;
}
/**
* Return `true` if the store contains the linked summary for the library
* with the given absolute [uri].
*/
bool hasLinkedLibrary(String uri) {
return linkedMap.containsKey(uri);
}
/**
* Return `true` if the store contains the unlinked summary for the unit
* with the given absolute [uri].
*/
bool hasUnlinkedUnit(String uri) {
return unlinkedMap.containsKey(uri);
}
void _fillMaps(String path, ResourceProvider resourceProvider) {
List<int> buffer;
if (resourceProvider != null) {
var file = resourceProvider.getFile(path);
buffer = file.readAsBytesSync();
} else {
io.File file = new io.File(path);
buffer = file.readAsBytesSync();
}
PackageBundle bundle = new PackageBundle.fromBuffer(buffer);
addBundle(path, bundle);
}
}