| // 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:math' show min; | |
| import 'package:analyzer/source/source.dart'; | |
| import 'package:analyzer/src/generated/engine.dart'; | |
| import 'package:analyzer/src/generated/source.dart' show UriResolver; | |
| import 'package:analyzer/src/summary2/package_bundle_format.dart'; | |
| import 'package:analyzer/src/util/platform_info.dart'; | |
| import 'package:path/path.dart' as path; | |
| /// 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(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 Source { | |
| @override | |
| final Uri uri; | |
| /// The summary file where this source was defined. | |
| final String summaryPath; | |
| final InSummarySourceKind kind; | |
| InSummarySource({ | |
| required this.uri, | |
| required this.summaryPath, | |
| required this.kind, | |
| }); | |
| @override | |
| TimestampedData<String> get contents => TimestampedData<String>(0, ''); | |
| @override | |
| String get fullName => '$uri'; | |
| @override | |
| int get hashCode => uri.hashCode; | |
| @override | |
| String get shortName => path.basename(fullName); | |
| @override | |
| bool operator ==(Object other) => other is Source && other.uri == uri; | |
| @override | |
| bool exists() => true; | |
| @override | |
| String toString() => uri.toString(); | |
| } | |
| enum InSummarySourceKind { library, part } | |
| /// 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) { | |
| var isLibrary = _dataStore._libraryUris.contains(uriString); | |
| return InSummarySource( | |
| uri: uri, | |
| summaryPath: summaryPath, | |
| kind: isLibrary | |
| ? InSummarySourceKind.library | |
| : InSummarySourceKind.part, | |
| ); | |
| } | |
| 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>{}; | |
| /// 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); | |
| } | |
| } |