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