| // 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); |
| } |
| } |