Skip to content

Fix #17: Add go to definition support for dot sourced file paths - #786

Merged
Rob Holt (rjmholt) merged 9 commits into
PowerShell:masterfrom
dee-see:go-to-dot-sourced
Dec 3, 2018
Merged

Rob Holt (rjmholt) merged 9 commits into
PowerShell:masterfrom
dee-see:go-to-dot-sourced

Conversation

@dee-see

Copy link
Copy Markdown
Contributor

Fix #17: Add go to definition support for dot sourced file paths

I would have liked to implement this with FindDeclarationVisitor but it didn't really make sense as the definition of a dot-sourcing is the file itself and there is no VisitFileAsAWhole method on the visitor!

I think the current implementation is a decent compromise though, let me know what you think.

As always, I'm working on Linux and I can't run release build/tests on my machine so I'll keep an eye on the AppVeyor builds.

Side note: I discovered an existing issue with Go To Definition when references were dot-sourced using Windows directory separators when running on Linux.

In this file:

. .\ReferenceFileA.ps1 # notice the backwards slash
Get-ChildItem

My-Function "testc"

You could F12 on My-Function and it would work, but in fact PSES didn't find anything in the "ReferencedFiles" because of the Windows directory separator and had to go over every file in the workspace to find the definition. This bug happens to be fixed by my implementation of Go-To-Dotsourced-Files.

@dee-see

Dominic (dee-see) commented Oct 27, 2018 •

Copy link
Copy Markdown
Contributor Author

Also, it's a bit strange that a dot-sourcing Symbol has a "Function" SymbolType. It causes a couple of quirks, the hover of the dot-source being "Function ./path.ps1" being one of them. Introducing a new
SymbolType seemed like an interesting idea to explore but I felt like it was a big enough job to be a PR by itself without any new features coming with it.

Fix build issues (net451 compatibility and public method comments)

Fix test

Make sure test is not platform-specific
@TylerLeonhardt

Copy link
Copy Markdown
Member

Dominic (@dee-see) thanks for this PR! Sorry for the delay. I was on vacation for a bit 😄

@rjmholt Rob Holt (rjmholt) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good Dominic (@dee-see). Left a couple of comments. Thanks for your contribution!

Comment thread src/PowerShellEditorServices/Language/LanguageService.cs Outdated
Comment thread src/PowerShellEditorServices/Language/LanguageService.cs Outdated
Comment thread src/PowerShellEditorServices/Language/LanguageService.cs Outdated

@rjmholt Rob Holt (rjmholt) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Thanks Dominic (@dee-see)!

@TylerLeonhardt

Copy link
Copy Markdown
Member

question - does this handle things like $PSScriptRoot? That would be a very common usecase of dot sourcing in my opinion.

@TylerLeonhardt

Copy link
Copy Markdown
Member

ping Dominic (@dee-see) :)

@dee-see

Dominic (dee-see) commented Nov 21, 2018 •

Copy link
Copy Markdown
Contributor Author

Sorry I missed the notification!

It searches the dot-sourced file relative to the file where the dot-sourcing happens (relative to $PSScriptRoot).

Is that what you meant? Or are you asking if it handles . "$PSScriptRoot/SomeScript.ps1"? In that case it doesn't (yet!).

@rjmholt

Copy link
Copy Markdown
Contributor

I think the second case. But if that's a significant work item in its own respect, I'm all for incremental improvement.

@dee-see

Copy link
Copy Markdown
Contributor Author

Ok, I'll look into it anyway. Up to you guys to decide if you prefer having it as a second pull request or as part of this one in the meantime. :)

Comment thread src/PowerShellEditorServices/Utility/PathUtils.cs Outdated

@TylerLeonhardt TylerLeonhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Just the one comment 🙂

@dee-see

Dominic (dee-see) commented Nov 24, 2018 •

Copy link
Copy Markdown
Contributor Author

Done!

I'll probably be able to handle $PSScriptRoot with a small change to FindDotSourcedVisitor. I just need to look around in the other projects (the analyzer and PowerShell itself) to see how automatic variables are handled so I don't reinvent the wheel here. If you want to wait for it, it will probably be done this weekend or the next.

@rjmholt

Copy link
Copy Markdown
Contributor

As far as I know, automatic variables are handled like any other variables -- they come from the ExecutionContext, which will come from the PowerShellContext in this project. Usually I'd imagine our best way of performing that extraction would just be (despite the overhead of needing to create a pipeline, it handles complexities like looking up the execution stack and centralises thread handling):

object x = null;
using (var pwsh = PowerShell.Create())
{
    x = pwsh.AddScript("$variable").Invoke();
}

BUT, $PSScriptRoot is only populated during the execution of a script, so by the time we get around to parsing the script, $PSScriptRoot won't exist anymore.

The nice thing is that $PSScriptRoot is for all intents and purposes a static constant (you can re-set it, but I doubt we'd be the only thing to break if you did). So it might be easiest to just substitute it for the absolute path of the script.

@rjmholt

Copy link
Copy Markdown
Contributor

Dominic (@dee-see) We can probably merge this as-is if you're happy? And then come back to $PSScriptRoot another time?

@dee-see

Copy link
Copy Markdown
Contributor Author

If you don't mind I'd keep this open until the next week-end. I'll continue working on $PSScriptRoot and wrap everything up then.

@SeeminglyScience

Copy link
Copy Markdown
Collaborator

BUT, $PSScriptRoot is only populated during the execution of a script, so by the time we get around to parsing the script, $PSScriptRoot won't exist anymore.

The nice thing is that $PSScriptRoot is for all intents and purposes a static constant (you can re-set it, but I doubt we'd be the only thing to break if you did). So it might be easiest to just substitute it for the absolute path of the script.

Yeah iirc it's set in the locals tuple every time a new script block invocation is started. It's set to the equivalent of something like Split-Path $MyInvocation.MyCommand.ScriptBlock.Ast.Extent.File

@rjmholt

Copy link
Copy Markdown
Contributor

If you don't mind I'd keep this open until the next week-end. I'll continue working on $PSScriptRoot and wrap everything up then.

Okie doke. We're overdue for a release and given this spate of PRs, I want to run a build at the end of this week, so it would be a good opportunity to test and get a fix out. But I don't want to force anything — just let me know when you're ready for review again :)

@dee-see

Copy link
Copy Markdown
Contributor Author

Ok I see! I have some free time tonight, I'll try to complete the $PSScriptRoot support. I've made quite a bit of progress last weekend so it isn't completely out of reach.

@dee-see

Dominic (dee-see) commented Nov 29, 2018 •

Copy link
Copy Markdown
Contributor Author

Rob Holt (@rjmholt) There you go. I had a cool solution with an AstVisitor that handled situations a bit more complex than a simple "$PSScriptRoot\SomeScript.ps1", but I had trouble fitting it in the LanguageService side and I was going down an unending rabbit hole. Sooo I stepped back and only handled the most simple (and by far most common!) case.

Those .Replace don't make me very proud but they work I guess.

One cool side effect of this is that Go to definition on functions now works across files that were dot-sourced using $PSScriptRoot.

@rjmholt Rob Holt (rjmholt) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really great Dominic (@dee-see). Left a couple of small comments

Comment thread src/PowerShellEditorServices/Language/AstOperations.cs Outdated
Comment thread src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs Outdated
Comment thread src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs
Comment thread src/PowerShellEditorServices/Language/LanguageService.cs Outdated
Comment thread src/PowerShellEditorServices/Workspace/ScriptFile.cs Outdated
Comment thread src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs Outdated
Comment thread src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs Outdated

@rkeithhill Keith Hill (rkeithhill) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@rjmholt Rob Holt (rjmholt) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving 😉

private static string GetDotSourcedPath(SymbolReference symbol, Workspace workspace, ScriptFile scriptFile)
{
string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"'));
return workspace.ResolveRelativeScriptPath(Path.GetDirectoryName(scriptFile.FilePath),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth factoring out the Path.GetDirectoryName(scriptFile.FilePath) as a variable here -- saves an allocation at least.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooop yes definitely, that was sloppy.

{
path = path.Replace(variableExpressionAst.ToString(), _psScriptRoot);
}
else

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than have an else clause, I would negate the top condition:

if (!(nestedExpression is VariableExpressionAst variableAst
      && variableAst.VariablePath.UserPath.Equals("PSScriptRoot", StringComparison.OrdinalIgnoreCase)))
{
    return null;
}

path = path.Replace(variableAst.ToString(), _psScriptRoot);

@rjmholt

Copy link
Copy Markdown
Contributor

Just want TylerLeonhardt to approve before we merge

@rjmholt

Copy link
Copy Markdown
Contributor

This may aid PowerShell/vscode-powershell#144 btw. That issue wants full intellisense I think (which would essentially require us to pre-emptively run the script or otherwise reimplement our completions), but will still help.

@TylerLeonhardt TylerLeonhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 left a nit

Comment thread src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add go to definition support for dot sourced file paths

5 participants