blob: a332f287968d75007fcac3fbce5cf86011d17ec4 [file] [log] [blame]
// Copyright (c) 2016, 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.
import 'dart:io' as io;
import 'dart:math' show min;
import 'dart:typed_data';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary2/package_bundle_format.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;
late final 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.
''';
}
@override
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;
}
}
/// 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 => TimestampedData<String>(0, '');
@Deprecated('Not used anymore')
@override
int get modificationStamp => 0;
@Deprecated('Use Source.uri instead')
@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 {
final SummaryDataStore _dataStore;
InSummaryUriResolver(this._dataStore);
@override
Uri? pathToUri(String path) => null;
@override
Source? resolveAbsolute(Uri uri) {
String uriString = uri.toString();
String? summaryPath = _dataStore.uriToSummaryPath[uriString];
if (summaryPath != null) {
return InSummarySource(uri, summaryPath);
}
return 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 [PackageBundleReader]s.
final List<PackageBundleReader> bundles = [];
/// Map from the URI of a unit to the summary path that contained it.
final Map<String, String?> uriToSummaryPath = <String, String?>{};
final Set<String> _libraryUris = <String>{};
final Set<String> _partUris = <String>{};
/// Create a [SummaryDataStore] and populate it with the summaries in
/// [summaryPaths].
SummaryDataStore(Iterable<String> summaryPaths,
{ResourceProvider? resourceProvider}) {
summaryPaths.forEach((String path) => _fillMaps(path, resourceProvider));
}
/// Add the given [bundle] loaded from the file with the given [path].
void addBundle(String? path, PackageBundleReader bundle) {
bundles.add(bundle);
for (var library in bundle.libraries) {
var libraryUri = library.uriStr;
_libraryUris.add(libraryUri);
for (var unit in library.units) {
var unitUri = unit.uriStr;
uriToSummaryPath[unitUri] = path;
if (unitUri != libraryUri) {
_partUris.add(unitUri);
}
}
}
}
/// Return `true` if the store contains the linked summary for the library
/// with the given absolute [uri].
bool hasLinkedLibrary(String uri) {
return _libraryUris.contains(uri);
}
/// Return `true` if the store contains the unlinked summary for the unit
/// with the given absolute [uri].
bool hasUnlinkedUnit(String uri) {
return uriToSummaryPath.containsKey(uri);
}
/// Return `true` if the unit with the [uri] is a part unit in the store.
bool isPartUnit(String uri) {
return _partUris.contains(uri);
}
void _fillMaps(String path, ResourceProvider? resourceProvider) {
Uint8List bytes;
if (resourceProvider != null) {
var file = resourceProvider.getFile(path);
bytes = file.readAsBytesSync();
} else {
io.File file = io.File(path);
bytes = file.readAsBytesSync();
}
var bundle = PackageBundleReader(bytes);
addBundle(path, bundle);
}
}