blob: 0fa12ae05c2ab484894a89dffcb0fac6ec881852 [file] [log] [blame]
// Copyright (c) 2014, 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 source.package_map_resolver;
import 'dart:core' hide Resource;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/util/asserts.dart' as asserts;
import 'package:path/path.dart' as pathos;
/**
* A [UriResolver] implementation for the `package:` scheme that uses a map of
* package names to their directories.
*/
class PackageMapUriResolver extends UriResolver {
/**
* The name of the `package` scheme.
*/
static const String PACKAGE_SCHEME = "package";
/**
* A table mapping package names to the path of the directories containing
* the package.
*/
final Map<String, List<Folder>> packageMap;
/**
* The [ResourceProvider] for this resolver.
*/
final ResourceProvider resourceProvider;
/**
* Create a new [PackageMapUriResolver].
*
* [packageMap] is a table mapping package names to the paths of the
* directories containing the package
*/
PackageMapUriResolver(this.resourceProvider, this.packageMap) {
asserts.notNull(resourceProvider);
asserts.notNull(packageMap);
}
@override
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
if (!isPackageUri(uri)) {
return null;
}
// Prepare path.
String path = uri.path;
// Prepare path components.
int index = path.indexOf('/');
if (index == -1 || index == 0) {
return null;
}
// <pkgName>/<relPath>
String pkgName = path.substring(0, index);
String relPath = path.substring(index + 1);
// Try to find an existing file.
List<Folder> packageDirs = packageMap[pkgName];
if (packageDirs != null) {
for (Folder packageDir in packageDirs) {
if (packageDir.exists) {
Resource result = packageDir.getChild(relPath);
if (result is File && result.exists) {
return result.createSource(uri);
}
}
}
}
// Return a NonExistingSource instance.
// This helps provide more meaningful error messages to users
// (a missing file error, as opposed to an invalid URI error).
String fullPath = packageDirs != null && packageDirs.isNotEmpty
? packageDirs.first.canonicalizePath(relPath)
: relPath;
return new NonExistingSource(fullPath, uri, UriKind.PACKAGE_URI);
}
@override
Uri restoreAbsolute(Source source) {
String sourcePath = source.fullName;
Uri bestMatch;
int bestMatchLength = -1;
pathos.Context pathContext = resourceProvider.pathContext;
for (String pkgName in packageMap.keys) {
List<Folder> pkgFolders = packageMap[pkgName];
for (int i = 0; i < pkgFolders.length; i++) {
Folder pkgFolder = pkgFolders[i];
String pkgFolderPath = pkgFolder.path;
if (pkgFolderPath.length > bestMatchLength &&
sourcePath.startsWith(pkgFolderPath + pathContext.separator)) {
String relPath = sourcePath.substring(pkgFolderPath.length + 1);
if (_isReversibleTranslation(pkgFolders, i, relPath)) {
List<String> relPathComponents = pathContext.split(relPath);
String relUriPath = pathos.posix.joinAll(relPathComponents);
bestMatch = Uri.parse('$PACKAGE_SCHEME:$pkgName/$relUriPath');
bestMatchLength = pkgFolderPath.length;
}
}
}
}
return bestMatch;
}
/**
* A translation from file path to package URI has just been found for
* using the [packageDirIndex]th element of [packageDirs], and appending the
* relative path [relPath]. Determine whether the translation is reversible;
* that is, whether translating the package URI pack to a file path will
* produce the file path we started with.
*/
bool _isReversibleTranslation(
List<Folder> packageDirs, int packageDirIndex, String relPath) {
// The translation is reversible provided there is no prior element of
// [packageDirs] containing a file matching [relPath].
for (int i = 0; i < packageDirIndex; i++) {
Folder packageDir = packageDirs[i];
if (packageDir.exists) {
Resource result = packageDir.getChild(relPath);
if (result is File && result.exists) {
return false;
}
}
}
return true;
}
/**
* Returns `true` if [uri] is a `package` URI.
*/
static bool isPackageUri(Uri uri) {
return uri.scheme == PACKAGE_SCHEME;
}
}