Skip to content
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

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

Merged
merged 9 commits into from
Dec 3, 2018
Merged

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

merged 9 commits into from
Dec 3, 2018

Conversation

dee-see
Copy link
Contributor

@dee-see dee-see commented Oct 27, 2018

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
Copy link
Contributor Author

dee-see commented Oct 27, 2018

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
Member

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

Copy link
Contributor

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

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

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

src/PowerShellEditorServices/Language/LanguageService.cs Outdated Show resolved Hide resolved
src/PowerShellEditorServices/Language/LanguageService.cs Outdated Show resolved Hide resolved
src/PowerShellEditorServices/Language/LanguageService.cs Outdated Show resolved Hide resolved
Copy link
Contributor

@rjmholt rjmholt left a comment

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 @dee-see!

@TylerLeonhardt
Copy link
Member

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

@TylerLeonhardt
Copy link
Member

ping @dee-see :)

@dee-see
Copy link
Contributor Author

dee-see commented Nov 21, 2018

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
Contributor

rjmholt commented Nov 22, 2018

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
Contributor Author

dee-see commented Nov 22, 2018

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. :)

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

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
Copy link
Contributor Author

dee-see commented Nov 24, 2018

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
Contributor

rjmholt commented Nov 24, 2018

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
Contributor

rjmholt commented Nov 28, 2018

@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
Contributor Author

dee-see commented Nov 28, 2018

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
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
Contributor

rjmholt commented Nov 28, 2018

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
Contributor Author

dee-see commented Nov 28, 2018

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
Copy link
Contributor Author

dee-see commented Nov 29, 2018

@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.

Copy link
Contributor

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

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

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

Copy link
Contributor

@rkeithhill rkeithhill left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@rjmholt rjmholt left a comment

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
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
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
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
Contributor

rjmholt commented Dec 2, 2018

Just want @TylerLeonhardt to approve before we merge

@rjmholt
Copy link
Contributor

rjmholt commented Dec 2, 2018

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.

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

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

LGTM 👍 left a nit

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