| // 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 analyzer.src.generated.source_io; |
| |
| import 'dart:collection'; |
| |
| import 'package:analyzer/exception/exception.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/java_io.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:path/path.dart' as path; |
| |
| export 'package:analyzer/src/generated/source.dart'; |
| |
| /** |
| * Instances of the class [DirectoryBasedSourceContainer] represent a source container that |
| * contains all sources within a given directory. |
| */ |
| @deprecated |
| class DirectoryBasedSourceContainer implements SourceContainer { |
| /** |
| * The container's path (not `null`). |
| */ |
| String _path; |
| |
| /** |
| * Construct a container representing the specified directory and containing any sources whose |
| * [Source.fullName] starts with the directory's path. This is a convenience method, |
| * fully equivalent to [DirectoryBasedSourceContainer.con2]. |
| * |
| * @param directory the directory (not `null`) |
| */ |
| DirectoryBasedSourceContainer.con1(JavaFile directory) |
| : this.con2(directory.getPath()); |
| |
| /** |
| * Construct a container representing the specified path and containing any sources whose |
| * [Source.fullName] starts with the specified path. |
| * |
| * @param path the path (not `null` and not empty) |
| */ |
| DirectoryBasedSourceContainer.con2(String path) { |
| this._path = _appendFileSeparator(path); |
| } |
| |
| @override |
| int get hashCode => _path.hashCode; |
| |
| /** |
| * Answer the receiver's path, used to determine if a source is contained in the receiver. |
| * |
| * @return the path (not `null`, not empty) |
| */ |
| String get path => _path; |
| |
| @override |
| bool operator ==(Object obj) => |
| (obj is DirectoryBasedSourceContainer) && obj.path == path; |
| |
| @override |
| bool contains(Source source) => source.fullName.startsWith(_path); |
| |
| @override |
| String toString() => "SourceContainer[$_path]"; |
| |
| /** |
| * Append the system file separator to the given path unless the path already ends with a |
| * separator. |
| * |
| * @param path the path to which the file separator is to be added |
| * @return a path that ends with the system file separator |
| */ |
| static String _appendFileSeparator(String path) { |
| if (path == null || |
| path.length <= 0 || |
| path.codeUnitAt(path.length - 1) == JavaFile.separatorChar) { |
| return path; |
| } |
| return "$path${JavaFile.separator}"; |
| } |
| } |
| |
| /** |
| * Instances of the class [ExplicitSourceResolver] map URIs to files on disk |
| * using a fixed mapping provided at construction time. |
| */ |
| @deprecated |
| class ExplicitSourceResolver extends UriResolver { |
| final Map<Uri, JavaFile> uriToFileMap; |
| final Map<String, Uri> pathToUriMap; |
| |
| /** |
| * Construct an [ExplicitSourceResolver] based on the given [uriToFileMap]. |
| */ |
| ExplicitSourceResolver(Map<Uri, JavaFile> uriToFileMap) |
| : uriToFileMap = uriToFileMap, |
| pathToUriMap = _computePathToUriMap(uriToFileMap); |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| JavaFile file = uriToFileMap[uri]; |
| actualUri ??= uri; |
| if (file == null) { |
| return new NonExistingSource( |
| uri.toString(), actualUri, UriKind.fromScheme(actualUri.scheme)); |
| } else { |
| return new FileBasedSource(file, actualUri); |
| } |
| } |
| |
| @override |
| Uri restoreAbsolute(Source source) { |
| return pathToUriMap[source.fullName]; |
| } |
| |
| /** |
| * Build the inverse mapping of [uriToSourceMap]. |
| */ |
| static Map<String, Uri> _computePathToUriMap( |
| Map<Uri, JavaFile> uriToSourceMap) { |
| Map<String, Uri> pathToUriMap = <String, Uri>{}; |
| uriToSourceMap.forEach((Uri uri, JavaFile file) { |
| pathToUriMap[file.getAbsolutePath()] = uri; |
| }); |
| return pathToUriMap; |
| } |
| } |
| |
| /** |
| * Instances of the class `FileBasedSource` implement a source that represents a file. |
| */ |
| class FileBasedSource extends Source { |
| /** |
| * A function that changes the way that files are read off of disk. |
| */ |
| static Function fileReadMode = (String s) => s; |
| |
| /** |
| * Map from encoded URI/filepath pair to a unique integer identifier. This |
| * identifier is used for equality tests and hash codes. |
| * |
| * The URI and filepath are joined into a pair by separating them with an '@' |
| * character. |
| */ |
| static final Map<String, int> _idTable = new HashMap<String, int>(); |
| |
| /** |
| * The URI from which this source was originally derived. |
| */ |
| @override |
| final Uri uri; |
| |
| /** |
| * The unique ID associated with this [FileBasedSource]. |
| */ |
| final int id; |
| |
| /** |
| * The file represented by this source. |
| */ |
| final JavaFile file; |
| |
| /** |
| * The cached absolute path of this source. |
| */ |
| String _absolutePath; |
| |
| /** |
| * The cached encoding for this source. |
| */ |
| String _encoding; |
| |
| /** |
| * Initialize a newly created source object to represent the given [file]. If |
| * a [uri] is given, then it will be used as the URI from which the source was |
| * derived, otherwise a `file:` URI will be created based on the [file]. |
| */ |
| FileBasedSource(JavaFile file, [Uri uri]) |
| : this.uri = uri ?? file.toURI(), |
| this.file = file, |
| id = _idTable.putIfAbsent( |
| '${uri ?? file.toURI()}@${file.getPath()}', () => _idTable.length); |
| |
| @override |
| TimestampedData<String> get contents { |
| return PerformanceStatistics.io.makeCurrentWhile(() { |
| return contentsFromFile; |
| }); |
| } |
| |
| /** |
| * Get the contents and timestamp of the underlying file. |
| * |
| * Clients should consider using the method [AnalysisContext.getContents] |
| * because contexts can have local overrides of the content of a source that the source is not |
| * aware of. |
| * |
| * @return the contents of the source paired with the modification stamp of the source |
| * @throws Exception if the contents of this source could not be accessed |
| * See [contents]. |
| */ |
| TimestampedData<String> get contentsFromFile { |
| return new TimestampedData<String>( |
| file.lastModified(), fileReadMode(file.readAsStringSync())); |
| } |
| |
| @override |
| String get encoding { |
| if (_encoding == null) { |
| _encoding = uri.toString(); |
| } |
| return _encoding; |
| } |
| |
| @override |
| String get fullName { |
| if (_absolutePath == null) { |
| _absolutePath = file.getAbsolutePath(); |
| } |
| return _absolutePath; |
| } |
| |
| @override |
| int get hashCode => uri.hashCode; |
| |
| @override |
| bool get isInSystemLibrary => uri.scheme == DartUriResolver.DART_SCHEME; |
| |
| @override |
| int get modificationStamp => file.lastModified(); |
| |
| @override |
| String get shortName => file.getName(); |
| |
| @override |
| UriKind get uriKind { |
| String scheme = uri.scheme; |
| return UriKind.fromScheme(scheme); |
| } |
| |
| @override |
| bool operator ==(Object object) { |
| if (object is FileBasedSource) { |
| return id == object.id; |
| } else if (object is Source) { |
| return uri == object.uri; |
| } |
| return false; |
| } |
| |
| @override |
| bool exists() => file.isFile(); |
| |
| @override |
| String toString() { |
| if (file == null) { |
| return "<unknown source>"; |
| } |
| return file.getAbsolutePath(); |
| } |
| } |
| |
| /** |
| * Instances of the class `FileUriResolver` resolve `file` URI's. |
| * |
| * This class is now deprecated, 'new FileUriResolver()' is equivalent to |
| * 'new ResourceUriResolver(PhysicalResourceProvider.INSTANCE)'. |
| */ |
| @deprecated |
| class FileUriResolver extends UriResolver { |
| /** |
| * The name of the `file` scheme. |
| */ |
| static String FILE_SCHEME = "file"; |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| if (!isFileUri(uri)) { |
| return null; |
| } |
| return new FileBasedSource(new JavaFile.fromUri(uri), actualUri ?? uri); |
| } |
| |
| @override |
| Uri restoreAbsolute(Source source) { |
| return new Uri.file(source.fullName); |
| } |
| |
| /** |
| * Return `true` if the given URI is a `file` URI. |
| * |
| * @param uri the URI being tested |
| * @return `true` if the given URI is a `file` URI |
| */ |
| static bool isFileUri(Uri uri) => uri.scheme == FILE_SCHEME; |
| } |
| |
| /** |
| * Instances of interface `LocalSourcePredicate` are used to determine if the given |
| * [Source] is "local" in some sense, so can be updated. |
| */ |
| abstract class LocalSourcePredicate { |
| /** |
| * Instance of [LocalSourcePredicate] that always returns `false`. |
| */ |
| static final LocalSourcePredicate FALSE = new LocalSourcePredicate_FALSE(); |
| |
| /** |
| * Instance of [LocalSourcePredicate] that always returns `true`. |
| */ |
| static final LocalSourcePredicate TRUE = new LocalSourcePredicate_TRUE(); |
| |
| /** |
| * Instance of [LocalSourcePredicate] that returns `true` for all [Source]s |
| * except of SDK. |
| */ |
| static final LocalSourcePredicate NOT_SDK = |
| new LocalSourcePredicate_NOT_SDK(); |
| |
| /** |
| * Determines if the given [Source] is local. |
| * |
| * @param source the [Source] to analyze |
| * @return `true` if the given [Source] is local |
| */ |
| bool isLocal(Source source); |
| } |
| |
| class LocalSourcePredicate_FALSE implements LocalSourcePredicate { |
| @override |
| bool isLocal(Source source) => false; |
| } |
| |
| class LocalSourcePredicate_NOT_SDK implements LocalSourcePredicate { |
| @override |
| bool isLocal(Source source) => source.uriKind != UriKind.DART_URI; |
| } |
| |
| class LocalSourcePredicate_TRUE implements LocalSourcePredicate { |
| @override |
| bool isLocal(Source source) => true; |
| } |
| |
| /** |
| * Instances of the class `PackageUriResolver` resolve `package` URI's in the context of |
| * an application. |
| * |
| * For the purposes of sharing analysis, the path to each package under the "packages" directory |
| * should be canonicalized, but to preserve relative links within a package, the remainder of the |
| * path from the package directory to the leaf should not. |
| */ |
| @deprecated |
| class PackageUriResolver extends UriResolver { |
| /** |
| * The name of the `package` scheme. |
| */ |
| static String PACKAGE_SCHEME = "package"; |
| |
| /** |
| * Log exceptions thrown with the message "Required key not available" only once. |
| */ |
| static bool _CanLogRequiredKeyIoException = true; |
| |
| /** |
| * The package directories that `package` URI's are assumed to be relative to. |
| */ |
| final List<JavaFile> _packagesDirectories; |
| |
| /** |
| * Initialize a newly created resolver to resolve `package` URI's relative to the given |
| * package directories. |
| * |
| * @param packagesDirectories the package directories that `package` URI's are assumed to be |
| * relative to |
| */ |
| PackageUriResolver(this._packagesDirectories) { |
| if (_packagesDirectories.length < 1) { |
| throw new ArgumentError( |
| "At least one package directory must be provided"); |
| } |
| } |
| |
| /** |
| * If the list of package directories contains one element, return it. |
| * Otherwise raise an exception. Intended for testing. |
| */ |
| String get packagesDirectory_forTesting { |
| int length = _packagesDirectories.length; |
| if (length != 1) { |
| throw new Exception('Expected 1 package directory, found $length'); |
| } |
| return _packagesDirectories[0].getPath(); |
| } |
| |
| /** |
| * Answer the canonical file for the specified package. |
| * |
| * @param packagesDirectory the "packages" directory (not `null`) |
| * @param pkgName the package name (not `null`, not empty) |
| * @param relPath the path relative to the package directory (not `null`, no leading slash, |
| * but may be empty string) |
| * @return the file (not `null`) |
| */ |
| JavaFile getCanonicalFile( |
| JavaFile packagesDirectory, String pkgName, String relPath) { |
| JavaFile pkgDir = new JavaFile.relative(packagesDirectory, pkgName); |
| try { |
| pkgDir = pkgDir.getCanonicalFile(); |
| } catch (exception, stackTrace) { |
| if (!exception.toString().contains("Required key not available")) { |
| AnalysisEngine.instance.logger.logError("Canonical failed: $pkgDir", |
| new CaughtException(exception, stackTrace)); |
| } else if (_CanLogRequiredKeyIoException) { |
| _CanLogRequiredKeyIoException = false; |
| AnalysisEngine.instance.logger.logError("Canonical failed: $pkgDir", |
| new CaughtException(exception, stackTrace)); |
| } |
| } |
| return new JavaFile.relative( |
| pkgDir, |
| relPath.replaceAll( |
| '/', new String.fromCharCode(JavaFile.separatorChar))); |
| } |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| if (!isPackageUri(uri)) { |
| return null; |
| } |
| String path = uri.path; |
| if (path == null) { |
| path = uri.path; |
| if (path == null) { |
| return null; |
| } |
| } |
| String pkgName; |
| String relPath; |
| int index = path.indexOf('/'); |
| if (index == -1) { |
| // No slash |
| pkgName = path; |
| relPath = ""; |
| } else if (index == 0) { |
| // Leading slash is invalid |
| return null; |
| } else { |
| // <pkgName>/<relPath> |
| pkgName = path.substring(0, index); |
| relPath = path.substring(index + 1); |
| } |
| for (JavaFile packagesDirectory in _packagesDirectories) { |
| JavaFile resolvedFile = new JavaFile.relative(packagesDirectory, path); |
| if (resolvedFile.exists()) { |
| JavaFile canonicalFile = |
| getCanonicalFile(packagesDirectory, pkgName, relPath); |
| if (actualUri != null) { |
| return new FileBasedSource(canonicalFile, actualUri); |
| } |
| if (_isSelfReference(packagesDirectory, canonicalFile)) { |
| uri = canonicalFile.toURI(); |
| } |
| return new FileBasedSource(canonicalFile, uri); |
| } |
| } |
| return new FileBasedSource( |
| getCanonicalFile(_packagesDirectories[0], pkgName, relPath), |
| actualUri ?? uri); |
| } |
| |
| @override |
| Uri restoreAbsolute(Source source) { |
| String sourceUri = _toFileUri(source.fullName); |
| for (JavaFile packagesDirectory in _packagesDirectories) { |
| List<JavaFile> pkgFolders = packagesDirectory.listFiles(); |
| if (pkgFolders != null) { |
| for (JavaFile pkgFolder in pkgFolders) { |
| try { |
| String pkgCanonicalUri = _toFileUri(pkgFolder.getCanonicalPath()); |
| if (sourceUri.startsWith(pkgCanonicalUri)) { |
| String relPath = sourceUri.substring(pkgCanonicalUri.length); |
| return Uri |
| .parse("$PACKAGE_SCHEME:${pkgFolder.getName()}$relPath"); |
| } |
| } catch (e) {} |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @return `true` if "file" was found in "packagesDir", and it is part of the "lib" folder |
| * of the application that contains in this "packagesDir". |
| */ |
| bool _isSelfReference(JavaFile packagesDir, JavaFile file) { |
| JavaFile rootDir = packagesDir.getParentFile(); |
| if (rootDir == null) { |
| return false; |
| } |
| String rootPath = rootDir.getAbsolutePath(); |
| String filePath = file.getAbsolutePath(); |
| return filePath.startsWith("$rootPath/lib"); |
| } |
| |
| /** |
| * Convert the given file path to a "file:" URI. On Windows, this transforms |
| * backslashes to forward slashes. |
| */ |
| String _toFileUri(String filePath) => path.context.toUri(filePath).toString(); |
| |
| /** |
| * Return `true` if the given URI is a `package` URI. |
| * |
| * @param uri the URI being tested |
| * @return `true` if the given URI is a `package` URI |
| */ |
| static bool isPackageUri(Uri uri) => PACKAGE_SCHEME == uri.scheme; |
| } |
| |
| /** |
| * Instances of the class `RelativeFileUriResolver` resolve `file` URI's. |
| * |
| * This class is now deprecated, file URI resolution should be done with |
| * ResourceUriResolver, i.e. |
| * 'new ResourceUriResolver(PhysicalResourceProvider.INSTANCE)'. |
| */ |
| @deprecated |
| class RelativeFileUriResolver extends UriResolver { |
| /** |
| * The name of the `file` scheme. |
| */ |
| static String FILE_SCHEME = "file"; |
| |
| /** |
| * The directories for the relatvie URI's |
| */ |
| final List<JavaFile> _relativeDirectories; |
| |
| /** |
| * The root directory for all the source trees |
| */ |
| final JavaFile _rootDirectory; |
| |
| /** |
| * Initialize a newly created resolver to resolve `file` URI's relative to the given root |
| * directory. |
| */ |
| RelativeFileUriResolver(this._rootDirectory, this._relativeDirectories) |
| : super(); |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| String rootPath = _rootDirectory.toURI().path; |
| String uriPath = uri.path; |
| if (uriPath != null && uriPath.startsWith(rootPath)) { |
| String filePath = uri.path.substring(rootPath.length); |
| for (JavaFile dir in _relativeDirectories) { |
| JavaFile file = new JavaFile.relative(dir, filePath); |
| if (file.exists()) { |
| return new FileBasedSource(file, actualUri ?? uri); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return `true` if the given URI is a `file` URI. |
| * |
| * @param uri the URI being tested |
| * @return `true` if the given URI is a `file` URI |
| */ |
| static bool isFileUri(Uri uri) => uri.scheme == FILE_SCHEME; |
| } |