blob: fd5f59f8cfd19f2aadec831d92a4abd4b8824dcc [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.
library kernel.import_table;
import 'ast.dart';
abstract class ImportTable {
int getImportIndex(Library library);
}
class ComponentImportTable implements ImportTable {
final Map<Library, int> _libraryIndex = <Library, int>{};
ComponentImportTable(Component component) {
for (int i = 0; i < component.libraries.length; ++i) {
_libraryIndex[component.libraries[i]] = i;
}
}
@override
int getImportIndex(Library library) => _libraryIndex[library] ?? -1;
}
class LibraryImportTable implements ImportTable {
final List<String> _importPaths = <String>[];
final List<Library> _importedLibraries = <Library>[];
final Map<Library, int> _libraryIndex = <Library, int>{};
factory LibraryImportTable(Library lib) {
return new _ImportTableBuilder(lib).build();
}
LibraryImportTable.empty();
/// The list of imports.
///
/// Should not be modified directly, as the index map would go out of sync.
List<String> get importPaths => _importPaths;
List<Library> get importedLibraries => _importedLibraries;
int addImport(Library target, String importPath) {
int? index = _libraryIndex[target];
if (index != null) return index;
index = _importPaths.length;
_importPaths.add(importPath);
_importedLibraries.add(target);
_libraryIndex[target] = index;
return index;
}
/// Returns the index of the given import, or -1 if not found.
@override
int getImportIndex(Library library) {
return _libraryIndex[library] ?? -1;
}
String getImportPath(Library library) {
return _importPaths[getImportIndex(library)];
}
}
/// Builds the import table for a given library.
class _ImportTableBuilder extends RecursiveVisitor {
final LibraryImportTable table = new LibraryImportTable.empty();
final Library referenceLibrary;
LibraryImportTable build() {
referenceLibrary.accept(this);
return table;
}
_ImportTableBuilder(this.referenceLibrary) {
table.addImport(referenceLibrary, '');
}
void addLibraryImport(Library? target) {
if (target == referenceLibrary) return; // Self-reference is special.
if (target == null) return;
Uri referenceUri = referenceLibrary.importUri;
Uri? targetUri = target.importUri;
// ignore: unnecessary_null_comparison
if (targetUri == null) {
throw '$referenceUri cannot refer to library without an import URI';
}
// To support using custom-uris in unit tests, we don't check directly
// whether the scheme is 'file:', but instead we check that is not 'dart:'
// or 'package:'.
bool isFileOrCustomScheme(Uri uri) =>
uri.scheme != '' && uri.scheme != 'package' && uri.scheme != 'dart';
bool isTargetSchemeFileOrCustom = isFileOrCustomScheme(targetUri);
bool isReferenceSchemeFileOrCustom = isFileOrCustomScheme(referenceUri);
if (isTargetSchemeFileOrCustom && isReferenceSchemeFileOrCustom) {
String relativeUri = relativeUriPath(targetUri, referenceUri);
table.addImport(target, relativeUri);
} else if (isTargetSchemeFileOrCustom) {
// Cannot import a file:URI from a dart:URI or package:URI.
// We may want to remove this restriction, but for now it's just a sanity
// check.
throw '$referenceUri cannot refer to application library $targetUri';
} else {
table.addImport(target, target.importUri.toString());
}
}
@override
void visitClassReference(Class node) {
addLibraryImport(node.enclosingLibrary);
}
@override
void visitLibrary(Library node) {
super.visitLibrary(node);
for (Reference exportedReference in node.additionalExports) {
addLibraryImport(exportedReference.node!.parent as Library);
}
}
@override
void defaultMemberReference(Member node) {
addLibraryImport(node.enclosingLibrary);
}
@override
void visitName(Name name) {
if (name.library != null) {
addLibraryImport(name.library);
}
}
}
/// DartDocTest(
/// relativeUriPath(
/// Uri.parse("file:///path/to/file1.dart"),
/// Uri.parse("file:///path/to/file2.dart"),
/// ),
/// "file1.dart"
/// )
/// DartDocTest(
/// relativeUriPath(
/// Uri.parse("file:///path/to/a/file1.dart"),
/// Uri.parse("file:///path/to/file2.dart"),
/// ),
/// "a/file1.dart"
/// )
/// DartDocTest(
/// relativeUriPath(
/// Uri.parse("file:///path/to/file1.dart"),
/// Uri.parse("file:///path/to/b/file2.dart"),
/// ),
/// "../file1.dart"
/// )
/// DartDocTest(
/// relativeUriPath(
/// Uri.parse("file:///path/to/file1.dart"),
/// Uri.parse("file:///different/path/to/file2.dart"),
/// ),
/// "../../../path/to/file1.dart"
/// )
String relativeUriPath(Uri target, Uri ref) {
List<String> targetSegments = target.pathSegments;
List<String> refSegments = ref.pathSegments;
int to = refSegments.length;
if (targetSegments.length < to) to = targetSegments.length;
to--; // The last entry is the filename, here we compare only directories.
int same = -1;
for (int i = 0; i < to; i++) {
if (targetSegments[i] == refSegments[i]) {
same = i;
} else {
break;
}
}
if (same == targetSegments.length - 2 &&
targetSegments.length == refSegments.length) {
// Both parts have the same number of segments,
// and they agree on all directories.
if (targetSegments.last == "") return ".";
return targetSegments.last;
}
List<String> path = <String>[];
int oked = same + 1;
while (oked < refSegments.length - 1) {
path.add("..");
oked++;
}
path.addAll(targetSegments.skip(same + 1));
if (path.isEmpty) path.add(".");
return path.join("/");
}