-
Notifications
You must be signed in to change notification settings - Fork 876
Match a path-mapped file name against the solution, not the current directory #20519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5c79cf4
df3c5db
77fa3b4
be9df78
4e680fc
8075288
8f8c79f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,32 @@ module internal Microsoft.VisualStudio.FSharp.Editor.CodeAnalysisExtensions | |
|
|
||
| open Microsoft.CodeAnalysis | ||
| open FSharp.Compiler.Text | ||
| open System | ||
| open System.IO | ||
|
|
||
| /// Whether the file name a compiler range carries is the file at this path. A build that maps its source | ||
| /// paths (`DeterministicSourcePaths`) leaves that name relative to a root the assembly never records, so a | ||
| /// relative one is matched by its tail rather than resolved against the process's current directory — | ||
| /// which is not that root, and belongs to whatever last set it. | ||
| let isTheFileAt (path: string) (fileName: string) = | ||
| // Paths, not identifiers: the file systems this runs on do not case them. | ||
| let comparison = StringComparison.OrdinalIgnoreCase | ||
|
|
||
| match path, fileName with | ||
| | null, _ | ||
| | _, null -> false | ||
| | path, rooted when Path.IsPathRooted rooted -> String.Equals(Path.GetFullPathSafe rooted, path, comparison) | ||
| | path, relative -> | ||
| let separator = string Path.DirectorySeparatorChar | ||
|
|
||
| let fromTheRoot = | ||
| relative.Split([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries) | ||
| |> Array.filter (fun segment -> segment <> ".") | ||
| |> String.concat separator | ||
|
|
||
| // Anchored on a separator so that a name matches whole directories, never the tail of one. | ||
| path.EndsWith($"{separator}{fromTheRoot}", comparison) | ||
|
|
||
| type Project with | ||
|
|
||
| /// Returns the projectIds of all projects within the same solution that directly reference this project | ||
|
|
@@ -80,13 +104,26 @@ type Solution with | |
| member self.GetAllProjectsThisProjectDependsOn(projectId: ProjectId) = | ||
| self.GetProjectIdsOfAllProjectReferences projectId |> Seq.map self.GetProject | ||
|
|
||
| /// The documents whose file is the one a compiler range names. A name a path map left relative | ||
| /// reaches no document through the workspace's index, which is keyed by the paths on disk, so it | ||
| /// is matched against those paths one by one instead. | ||
| member self.GetDocumentIdsWithFSharpFileName(fileName: string) = | ||
| match fileName with | ||
| | null -> [] | ||
| | rooted when Path.IsPathRooted rooted -> self.GetDocumentIdsWithFilePath(Path.GetFullPathSafe rooted) |> List.ofSeq | ||
| | relative -> | ||
| [ | ||
| for project in self.Projects do | ||
| for document in project.Documents do | ||
| if relative |> isTheFileAt document.FilePath then | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖🕵️ [P2] Repeated external-declaration lookups add seconds and gigabytes of allocations to Find All References. // ExternalLibrary.dll was built from Library.fs with --pathmap:C:\package=.\
// Solution: 200 projects, 100 documents each, all referencing that DLL.
// Library.fs is not in the solution. Find All References on value:
let result = ExternalLibrary.value
// Repeated declaration lookup input: .\\Library.fs |
||
| document.Id | ||
| ] | ||
|
|
||
| /// Try to retrieve the corresponding DocumentId for the range's file in the solution | ||
| /// and if a projectId is provided, only try to find the document within that project | ||
| /// or a project referenced by that project | ||
| member self.TryGetDocumentIdFromFSharpRange(range: range, ?projectId: ProjectId) = | ||
|
|
||
| let filePath = System.IO.Path.GetFullPathSafe range.FileName | ||
|
|
||
| let checkProjectId (docId: DocumentId) = | ||
| if projectId.IsSome then | ||
| docId.ProjectId = projectId.Value | ||
|
|
@@ -107,7 +144,7 @@ type Solution with | |
| matchingDoc tail | ||
| | None -> Some docId | ||
|
|
||
| self.GetDocumentIdsWithFilePath filePath |> List.ofSeq |> matchingDoc | ||
| self.GetDocumentIdsWithFSharpFileName range.FileName |> matchingDoc | ||
|
|
||
| /// Try to retrieve the corresponding Document for the range's file in the solution | ||
| /// and if a projectId is provided, only try to find the document within that project | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,7 +64,7 @@ type FSharpSymbolUse with | |
| Some(SymbolScope.Projects([ currentDocument.Project ], isSymbolLocalForProject)) | ||
| else | ||
| let projects = | ||
| currentDocument.Project.Solution.GetDocumentIdsWithFilePath(filePath) | ||
| currentDocument.Project.Solution.GetDocumentIdsWithFSharpFileName loc.FileName | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖🕵️ [P1] Find All References loses the consumer's reference when an unrelated document has the mapped suffix. The executed service returns 0 references here; base returns 1. The suffix match selects only // C:\package\Library.fs -> ExternalLibrary.dll, --pathmap:C:\package=.
module ExternalLibrary
let value = 42
// Consumer/App.fs; references ExternalLibrary.dll
module Consumer
let result = ExternalLibrary.value // Find All References on value
// Unrelated/Library.fs; separate solution project, no reference to the DLL
module Unrelated
let value = 99 |
||
| |> Seq.map (fun x -> x.ProjectId) | ||
| |> Seq.distinct | ||
| |> Seq.map currentDocument.Project.Solution.GetProject | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤖🕵️ [P2] Foreign mapped filenames now throw during document lookup on the editor's .NET Framework runtime. An imported DLL carrying this filename previously returned no matches;
Path.IsPathRootednow throws beforeGetFullPathSafecan protect the lookup. Keep invalid/non-native filenames on a non-throwing path.