| File: SourceFileResolver.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { /// <summary> /// Resolves references to source files specified in source code. /// </summary> public class SourceFileResolver : SourceReferenceResolver, IEquatable<SourceFileResolver> { public static SourceFileResolver Default { get; } = new SourceFileResolver(ImmutableArray<string>.Empty, baseDirectory: null); private readonly string? _baseDirectory; private readonly ImmutableArray<string> _searchPaths; private readonly ImmutableArray<KeyValuePair<string, string>> _pathMap; public SourceFileResolver(IEnumerable<string> searchPaths, string? baseDirectory) : this(searchPaths.AsImmutableOrNull(), baseDirectory) { } public SourceFileResolver(ImmutableArray<string> searchPaths, string? baseDirectory) : this(searchPaths, baseDirectory, ImmutableArray<KeyValuePair<string, string>>.Empty) { } public SourceFileResolver( ImmutableArray<string> searchPaths, string? baseDirectory, ImmutableArray<KeyValuePair<string, string>> pathMap) { if (searchPaths.IsDefault) { throw new ArgumentNullException(nameof(searchPaths)); } if (baseDirectory != null && PathUtilities.GetPathKind(baseDirectory) != PathKind.Absolute) { throw new ArgumentException(CodeAnalysisResources.AbsolutePathExpected, nameof(baseDirectory)); } _baseDirectory = baseDirectory; _searchPaths = searchPaths; // The previous public API required paths to not end with a path separator. // This broke handling of root paths (e.g. "/" cannot be represented), so // the new requirement is for paths to always end with a path separator. // However, because this is a public API, both conventions must be allowed, // so normalize the paths here (instead of enforcing end-with-sep). if (!pathMap.IsDefaultOrEmpty) { var pathMapBuilder = ArrayBuilder<KeyValuePair<string, string>>.GetInstance(pathMap.Length); foreach (var (key, value) in pathMap) { if (key == null || key.Length == 0) { throw new ArgumentException(CodeAnalysisResources.EmptyKeyInPathMap, nameof(pathMap)); } if (value == null) { throw new ArgumentException(CodeAnalysisResources.NullValueInPathMap, nameof(pathMap)); } var normalizedKey = PathUtilities.EnsureTrailingSeparator(key); var normalizedValue = PathUtilities.EnsureTrailingSeparator(value); pathMapBuilder.Add(new KeyValuePair<string, string>(normalizedKey, normalizedValue)); } _pathMap = pathMapBuilder.ToImmutableAndFree(); } else { _pathMap = ImmutableArray<KeyValuePair<string, string>>.Empty; } } public string? BaseDirectory => _baseDirectory; public ImmutableArray<string> SearchPaths => _searchPaths; public ImmutableArray<KeyValuePair<string, string>> PathMap => _pathMap; public override string? NormalizePath(string path, string? baseFilePath) { string? normalizedPath = FileUtilities.NormalizeRelativePath(path, baseFilePath, _baseDirectory); return (normalizedPath == null || _pathMap.IsDefaultOrEmpty) ? normalizedPath : PathUtilities.NormalizePathPrefix(normalizedPath, _pathMap); } public override string? ResolveReference(string path, string? baseFilePath) { string? resolvedPath = FileUtilities.ResolveRelativePath(path, baseFilePath, _baseDirectory, _searchPaths, FileExists); if (resolvedPath == null) { return null; } return FileUtilities.TryNormalizeAbsolutePath(resolvedPath); } public override Stream OpenRead(string resolvedPath) { CompilerPathUtilities.RequireAbsolutePath(resolvedPath, nameof(resolvedPath)); return FileUtilities.OpenRead(resolvedPath); } protected virtual bool FileExists([NotNullWhen(true)] string? resolvedPath) { return File.Exists(resolvedPath); } public override bool Equals(object? obj) { // Explicitly check that we're not comparing against a derived type if (obj == null || GetType() != obj.GetType()) { return false; } return Equals((SourceFileResolver)obj); } public bool Equals(SourceFileResolver? other) { if (other is null) { return false; } return string.Equals(_baseDirectory, other._baseDirectory, StringComparison.Ordinal) && _searchPaths.SequenceEqual(other._searchPaths, StringComparer.Ordinal) && _pathMap.SequenceEqual(other._pathMap); } public override int GetHashCode() { return Hash.Combine(_baseDirectory != null ? StringComparer.Ordinal.GetHashCode(_baseDirectory) : 0, Hash.Combine(Hash.CombineValues(_searchPaths, StringComparer.Ordinal), Hash.CombineValues(_pathMap))); } } }