/// <summary> /// Performs semantic analysis on the given ScriptFile and returns /// an array of ScriptFileMarkers. /// </summary> /// <param name="file">The ScriptFile which will be analyzed for semantic markers.</param> /// <returns>An array of ScriptFileMarkers containing semantic analysis results.</returns> public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file) { if (file.IsAnalysisEnabled) { // TODO: This is a temporary fix until we can change how // ScriptAnalyzer invokes their async tasks. Task<ScriptFileMarker[]> analysisTask = Task.Factory.StartNew<ScriptFileMarker[]>( () => { return this.scriptAnalyzer .AnalyzeSyntaxTree( file.ScriptAst, file.ScriptTokens, file.FilePath) .Select(ScriptFileMarker.FromDiagnosticRecord) .ToArray(); }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); return analysisTask.Result; } else { // Return an empty marker list return new ScriptFileMarker[0]; } }
/// <summary> /// Sets the list of breakpoints for the current debugging session. /// </summary> /// <param name="scriptFile">The ScriptFile in which breakpoints will be set.</param> /// <param name="lineNumbers">The line numbers at which breakpoints will be set.</param> /// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param> /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns> public async Task<BreakpointDetails[]> SetBreakpoints( ScriptFile scriptFile, int[] lineNumbers, bool clearExisting = true) { IEnumerable<Breakpoint> resultBreakpoints = null; if (clearExisting) { await this.ClearBreakpointsInFile(scriptFile); } if (lineNumbers.Length > 0) { // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. string escapedScriptPath = PowerShellContext.EscapeWildcardsInPath(scriptFile.FilePath); PSCommand psCommand = new PSCommand(); psCommand.AddCommand("Set-PSBreakpoint"); psCommand.AddParameter("Script", escapedScriptPath); psCommand.AddParameter("Line", lineNumbers.Length > 0 ? lineNumbers : null); resultBreakpoints = await this.powerShellContext.ExecuteCommand<Breakpoint>( psCommand); return resultBreakpoints .Select(BreakpointDetails.Create) .ToArray(); } return new BreakpointDetails[0]; }
/// <summary> /// Gets an open file in the workspace. If the file isn't open but /// exists on the filesystem, load and return it. /// </summary> /// <param name="filePath">The file path at which the script resides.</param> /// <exception cref="FileNotFoundException"> /// <paramref name="filePath"/> is not found. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="filePath"/> contains a null or empty string. /// </exception> public ScriptFile GetFile(string filePath) { Validate.IsNotNullOrEmptyString("filePath", filePath); // Resolve the full file path string resolvedFilePath = this.ResolveFilePath(filePath); string keyName = resolvedFilePath.ToLower(); // Make sure the file isn't already loaded into the workspace ScriptFile scriptFile = null; if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) { // This method allows FileNotFoundException to bubble up // if the file isn't found. using (StreamReader streamReader = new StreamReader(resolvedFilePath, Encoding.UTF8)) { scriptFile = new ScriptFile(resolvedFilePath, filePath, streamReader); this.workspaceFiles.Add(keyName, scriptFile); } Logger.Write(LogLevel.Verbose, "Opened file on disk: " + resolvedFilePath); } return scriptFile; }
/// <summary> /// Sets the list of breakpoints for the current debugging session. /// </summary> /// <param name="scriptFile">The ScriptFile in which breakpoints will be set.</param> /// <param name="lineNumbers">The line numbers at which breakpoints will be set.</param> /// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param> /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns> public async Task<BreakpointDetails[]> SetBreakpoints( ScriptFile scriptFile, int[] lineNumbers, bool clearExisting = true) { IEnumerable<Breakpoint> resultBreakpoints = null; if (clearExisting) { await this.ClearBreakpointsInFile(scriptFile); } if (lineNumbers.Length > 0) { PSCommand psCommand = new PSCommand(); psCommand.AddCommand("Set-PSBreakpoint"); psCommand.AddParameter("Script", scriptFile.FilePath); psCommand.AddParameter("Line", lineNumbers.Length > 0 ? lineNumbers : null); resultBreakpoints = await this.powerShellContext.ExecuteCommand<Breakpoint>( psCommand); return resultBreakpoints .Select(BreakpointDetails.Create) .ToArray(); } return new BreakpointDetails[0]; }
/// <summary> /// Gets completions for a statement contained in the given /// script file at the specified line and column position. /// </summary> /// <param name="scriptFile"> /// The script file in which completions will be gathered. /// </param> /// <param name="lineNumber"> /// The 1-based line number at which completions will be gathered. /// </param> /// <param name="columnNumber"> /// The 1-based column number at which completions will be gathered. /// </param> /// <returns> /// A CommandCompletion instance completions for the identified statement. /// </returns> public async Task<CompletionResults> GetCompletionsInFile( ScriptFile scriptFile, int lineNumber, int columnNumber) { Validate.IsNotNull("scriptFile", scriptFile); // Get the offset at the specified position. This method // will also validate the given position. int fileOffset = scriptFile.GetOffsetAtPosition( lineNumber, columnNumber); RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandle(); CompletionResults completionResults = AstOperations.GetCompletions( scriptFile.ScriptAst, scriptFile.ScriptTokens, fileOffset, runspaceHandle.Runspace); runspaceHandle.Dispose(); // save state of most recent completion mostRecentCompletions = completionResults; mostRecentRequestFile = scriptFile.Id; mostRecentRequestLine = lineNumber; mostRecentRequestOffest = columnNumber; return completionResults; }
internal static CompletionResults Create( ScriptFile scriptFile, CommandCompletion commandCompletion) { return new CompletionResults { Completions = GetCompletionsArray(commandCompletion), ReplacedRange = scriptFile.GetRangeBetweenOffsets( commandCompletion.ReplacementIndex, commandCompletion.ReplacementIndex + commandCompletion.ReplacementLength) }; }
/// <summary> /// Gets completions for a statement contained in the given /// script file at the specified line and column position. /// </summary> /// <param name="scriptFile"> /// The script file in which completions will be gathered. /// </param> /// <param name="lineNumber"> /// The 1-based line number at which completions will be gathered. /// </param> /// <param name="columnNumber"> /// The 1-based column number at which completions will be gathered. /// </param> /// <returns> /// A CommandCompletion instance completions for the identified statement. /// </returns> public async Task<CompletionResults> GetCompletionsInFile( ScriptFile scriptFile, int lineNumber, int columnNumber) { Validate.IsNotNull("scriptFile", scriptFile); // Get the offset at the specified position. This method // will also validate the given position. int fileOffset = scriptFile.GetOffsetAtPosition( lineNumber, columnNumber); RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandle( new CancellationTokenSource(DefaultWaitTimeoutMilliseconds).Token); CommandCompletion commandCompletion = AstOperations.GetCompletions( scriptFile.ScriptAst, scriptFile.ScriptTokens, fileOffset, runspaceHandle.Runspace); runspaceHandle.Dispose(); if (commandCompletion != null) { CompletionResults completionResults = CompletionResults.Create( scriptFile, commandCompletion); // save state of most recent completion mostRecentCompletions = completionResults; mostRecentRequestFile = scriptFile.Id; mostRecentRequestLine = lineNumber; mostRecentRequestOffest = columnNumber; return completionResults; } else { return new CompletionResults(); } }
/// <summary> /// Gets all file references by recursively searching /// through referenced files in a scriptfile /// </summary> /// <param name="scriptFile">Contains the details and contents of an open script file</param> /// <returns>A scriptfile array where the first file /// in the array is the "root file" of the search</returns> public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) { Dictionary<string, ScriptFile> referencedScriptFiles = new Dictionary<string, ScriptFile>(); List<ScriptFile> expandedReferences = new List<ScriptFile>(); // add original file so it's not searched for, then find all file references referencedScriptFiles.Add(scriptFile.Id, scriptFile); RecursivelyFindReferences(scriptFile, referencedScriptFiles); // remove original file from referened file and add it as the first element of the // expanded referenced list to maintain order so the original file is always first in the list referencedScriptFiles.Remove(scriptFile.Id); expandedReferences.Add(scriptFile); if (referencedScriptFiles.Count > 0) { expandedReferences.AddRange(referencedScriptFiles.Values); } return expandedReferences.ToArray(); }
private void AssertFileChange( string initialString, string expectedString, FileChange fileChange) { using (StringReader stringReader = new StringReader(initialString)) { // Create an in-memory file from the StringReader ScriptFile fileToChange = new ScriptFile("TestFile.ps1", "TestFile.ps1", stringReader); // Apply the FileChange and assert the resulting contents fileToChange.ApplyChange(fileChange); Assert.Equal(expectedString, fileToChange.Contents); } }
/// <summary> /// Recusrively searches through referencedFiles in scriptFiles /// and builds a Dictonary of the file references /// </summary> /// <param name="scriptFile">Details an contents of "root" script file</param> /// <param name="referencedScriptFiles">A Dictionary of referenced script files</param> private void RecursivelyFindReferences( ScriptFile scriptFile, Dictionary<string, ScriptFile> referencedScriptFiles) { // Get the base path of the current script for use in resolving relative paths string baseFilePath = GetBaseFilePath( scriptFile.FilePath); ScriptFile referencedFile; foreach (string referencedFileName in scriptFile.ReferencedFiles) { string resolvedScriptPath = this.ResolveRelativeScriptPath( baseFilePath, referencedFileName); // Make sure file exists before trying to get the file if (File.Exists(resolvedScriptPath)) { // Get the referenced file if it's not already in referencedScriptFiles referencedFile = this.GetFile(resolvedScriptPath); // Normalize the resolved script path and add it to the // referenced files list if it isn't there already resolvedScriptPath = resolvedScriptPath.ToLower(); if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) { referencedScriptFiles.Add(resolvedScriptPath, referencedFile); RecursivelyFindReferences(referencedFile, referencedScriptFiles); } } } }
/// <summary> /// Closes a currently open script file with the given file path. /// </summary> /// <param name="scriptFile">The file path at which the script resides.</param> public void CloseFile(ScriptFile scriptFile) { Validate.IsNotNull("scriptFile", scriptFile); this.workspaceFiles.Remove(scriptFile.Id); }
/// <summary> /// Perform semantic analysis on the given ScriptFile with the given settings. /// </summary> /// <param name="file">The ScriptFile to be analyzed.</param> /// <param name="settings">ScriptAnalyzer settings</param> /// <returns></returns> public async Task <ScriptFileMarker[]> GetSemanticMarkersAsync(ScriptFile file, Hashtable settings) { return(await GetSemanticMarkersAsync <Hashtable>(file, null, settings)); }
/// <summary> /// Sets the list of line breakpoints for the current debugging session. /// </summary> /// <param name="scriptFile">The ScriptFile in which breakpoints will be set.</param> /// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param> /// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param> /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns> public async Task <BreakpointDetails[]> SetLineBreakpoints( ScriptFile scriptFile, BreakpointDetails[] breakpoints, bool clearExisting = true) { var resultBreakpointDetails = new List <BreakpointDetails>(); if (clearExisting) { await this.ClearBreakpointsInFile(scriptFile); } if (breakpoints.Length > 0) { // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. string escapedScriptPath = PowerShellContext.EscapePath(scriptFile.FilePath, escapeSpaces: false); foreach (BreakpointDetails breakpoint in breakpoints) { PSCommand psCommand = new PSCommand(); psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint"); psCommand.AddParameter("Script", escapedScriptPath); psCommand.AddParameter("Line", breakpoint.LineNumber); // Check if the user has specified the column number for the breakpoint. if (breakpoint.ColumnNumber.HasValue) { // It bums me out that PowerShell will silently ignore a breakpoint // where either the line or the column is invalid. I'd rather have an // error or warning message I could relay back to the client. psCommand.AddParameter("Column", breakpoint.ColumnNumber.Value); } // Check if this is a "conditional" line breakpoint. if (breakpoint.Condition != null) { ScriptBlock actionScriptBlock = GetBreakpointActionScriptBlock(breakpoint); // If there was a problem with the condition string, // move onto the next breakpoint. if (actionScriptBlock == null) { resultBreakpointDetails.Add(breakpoint); continue; } psCommand.AddParameter("Action", actionScriptBlock); } IEnumerable <Breakpoint> configuredBreakpoints = await this.powerShellContext.ExecuteCommand <Breakpoint>(psCommand); // The order in which the breakpoints are returned is significant to the // VSCode client and should match the order in which they are passed in. resultBreakpointDetails.AddRange( configuredBreakpoints.Select(BreakpointDetails.Create)); } } return(resultBreakpointDetails.ToArray()); }
public void FindsDotSourcedFiles() { string exampleScriptContents = @". .\athing.ps1"+"\r\n"+ @". .\somefile.ps1"+"\r\n" + @". .\somefile.ps1"+"\r\n" + @"Do-Stuff $uri"+"\r\n" + @". simpleps.ps1"; using (StringReader stringReader = new StringReader(exampleScriptContents)) { ScriptFile scriptFile = new ScriptFile("DotSourceTestFile.ps1", "DotSourceTestFile.ps1", stringReader); Assert.Equal(3, scriptFile.ReferencedFiles.Length); System.Console.Write("a" + scriptFile.ReferencedFiles[0]); Assert.Equal(@".\athing.ps1", scriptFile.ReferencedFiles[0]); } }
/// <summary> /// Finds the details of the symbol at the given script file location. /// </summary> /// <param name="scriptFile">The ScriptFile in which the symbol can be located.</param> /// <param name="lineNumber">The line number at which the symbol can be located.</param> /// <param name="columnNumber">The column number at which the symbol can be located.</param> /// <returns></returns> public async Task<SymbolDetails> FindSymbolDetailsAtLocation( ScriptFile scriptFile, int lineNumber, int columnNumber) { SymbolDetails symbolDetails = null; SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, columnNumber); if (symbolReference != null) { RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandle(); symbolReference.FilePath = scriptFile.FilePath; symbolDetails = new SymbolDetails(symbolReference, runspaceHandle.Runspace); runspaceHandle.Dispose(); } else { // TODO #21: Return Result<T> return null; } return symbolDetails; }
/// <summary> /// Finds all the references of a symbol /// </summary> /// <param name="foundSymbol">The symbol to find all references for</param> /// <param name="referencedFiles">An array of scriptFiles too search for references in</param> /// <returns>FindReferencesResult</returns> public async Task<FindReferencesResult> FindReferencesOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles) { if (foundSymbol != null) { int symbolOffset = referencedFiles[0].GetOffsetAtPosition( foundSymbol.ScriptRegion.StartLineNumber, foundSymbol.ScriptRegion.StartColumnNumber); // Make sure aliases have been loaded await GetAliases(); List<SymbolReference> symbolReferences = new List<SymbolReference>(); foreach (ScriptFile file in referencedFiles) { IEnumerable<SymbolReference> symbolReferencesinFile = AstOperations .FindReferencesOfSymbol( file.ScriptAst, foundSymbol, CmdletToAliasDictionary, AliasToCmdletDictionary) .Select( reference => { reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); reference.FilePath = file.FilePath; return reference; }); symbolReferences.AddRange(symbolReferencesinFile); } return new FindReferencesResult { SymbolFileOffset = symbolOffset, SymbolName = foundSymbol.SymbolName, FoundReferences = symbolReferences }; } else { return null; } }
protected abstract IEnumerable <SymbolReference> GetSymbolsImpl(ScriptFile scriptFile, Version psVersion);
/// <summary> /// Finds command completion details for the script given a file location /// </summary> /// <param name="file">The details and contents of a open script file</param> /// <param name="entryName">The name of the suggestion that needs details</param> /// <returns>CompletionResult object (contains information about the command completion)</returns> public CompletionDetails GetCompletionDetailsInFile( ScriptFile file, string entryName) { // Makes sure the most recent completions request was the same line and column as this request if (file.Id.Equals(mostRecentRequestFile)) { CompletionDetails completionResult = mostRecentCompletions.Completions.FirstOrDefault( result => result.CompletionText.Equals(entryName)); return completionResult; } else { return null; } }
/// <summary> /// Finds the details of the symbol at the given script file location. /// </summary> /// <param name="scriptFile">The ScriptFile in which the symbol can be located.</param> /// <param name="lineNumber">The line number at which the symbol can be located.</param> /// <param name="columnNumber">The column number at which the symbol can be located.</param> /// <returns></returns> public async Task<SymbolDetails> FindSymbolDetailsAtLocation( ScriptFile scriptFile, int lineNumber, int columnNumber) { SymbolDetails symbolDetails = null; SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, columnNumber); if (symbolReference != null) { // Request a runspace handle with a short timeout RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandle( new CancellationTokenSource(DefaultWaitTimeoutMilliseconds).Token); symbolReference.FilePath = scriptFile.FilePath; symbolDetails = new SymbolDetails(symbolReference, runspaceHandle.Runspace); runspaceHandle.Dispose(); } else { // TODO #21: Return Result<T> return null; } return symbolDetails; }
protected override bool CanProvideFor(ScriptFile scriptFile) { return((scriptFile.FilePath != null && scriptFile.FilePath.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) || AstOperations.IsPowerShellDataFileAst(scriptFile.ScriptAst)); }
protected override bool CanProvideFor(ScriptFile scriptFile) { return(scriptFile.FilePath.EndsWith("tests.ps1", StringComparison.OrdinalIgnoreCase)); }
/// <summary> /// Gets a new ScriptFile instance which is identified by the given file /// path and initially contains the given buffer contents. /// </summary> /// <param name="filePath"></param> /// <param name="initialBuffer"></param> /// <returns></returns> public ScriptFile GetFileBuffer(string filePath, string initialBuffer) { Validate.IsNotNullOrEmptyString("filePath", filePath); // Resolve the full file path string resolvedFilePath = this.ResolveFilePath(filePath); string keyName = resolvedFilePath.ToLower(); // Make sure the file isn't already loaded into the workspace ScriptFile scriptFile = null; if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) { scriptFile = new ScriptFile(resolvedFilePath, filePath, initialBuffer); this.workspaceFiles.Add(keyName, scriptFile); Logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); } return scriptFile; }
/// <summary> /// Finds all the occurences of a symbol in the script given a file location /// </summary> /// <param name="file">The details and contents of a open script file</param> /// <param name="lineNumber">The line number of the cursor for the given script</param> /// <param name="columnNumber">The coulumn number of the cursor for the given script</param> /// <returns>FindOccurrencesResult</returns> public FindOccurrencesResult FindOccurrencesInFile( ScriptFile file, int lineNumber, int columnNumber) { SymbolReference foundSymbol = AstOperations.FindSymbolAtPosition( file.ScriptAst, lineNumber, columnNumber); if (foundSymbol != null) { // find all references, and indicate that looking for aliases is not needed IEnumerable<SymbolReference> symbolOccurrences = AstOperations .FindReferencesOfSymbol( file.ScriptAst, foundSymbol, false); return new FindOccurrencesResult { FoundOccurrences = symbolOccurrences }; } else { return null; } }
private async Task ClearBreakpointsInFile(ScriptFile scriptFile) { List<Breakpoint> breakpoints = null; // Get the list of breakpoints for this file if (this.breakpointsPerFile.TryGetValue(scriptFile.Id, out breakpoints)) { if (breakpoints.Count > 0) { PSCommand psCommand = new PSCommand(); psCommand.AddCommand("Remove-PSBreakpoint"); psCommand.AddParameter("Breakpoint", breakpoints.ToArray()); await this.powerShellContext.ExecuteCommand<object>(psCommand); // Clear the existing breakpoints list for the file breakpoints.Clear(); } } }
protected abstract bool CanProvideFor(ScriptFile scriptFile);
/// <summary> /// Finds the definition of a symbol in the script file or any of the /// files that it references. /// </summary> /// <param name="sourceFile">The initial script file to be searched for the symbol's definition.</param> /// <param name="foundSymbol">The symbol for which a definition will be found.</param> /// <param name="workspace">The Workspace to which the ScriptFile belongs.</param> /// <returns>The resulting GetDefinitionResult for the symbol's definition.</returns> public async Task <GetDefinitionResult> GetDefinitionOfSymbol( ScriptFile sourceFile, SymbolReference foundSymbol, Workspace workspace) { Validate.IsNotNull("sourceFile", sourceFile); Validate.IsNotNull("foundSymbol", foundSymbol); Validate.IsNotNull("workspace", workspace); ScriptFile[] referencedFiles = workspace.ExpandScriptReferences( sourceFile); var filesSearched = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // look through the referenced files until definition is found // or there are no more file to look through SymbolReference foundDefinition = null; for (int i = 0; i < referencedFiles.Length; i++) { foundDefinition = AstOperations.FindDefinitionOfSymbol( referencedFiles[i].ScriptAst, foundSymbol); filesSearched.Add(referencedFiles[i].FilePath); if (foundDefinition != null) { foundDefinition.FilePath = referencedFiles[i].FilePath; break; } } // if the definition the not found in referenced files // look for it in all the files in the workspace if (foundDefinition == null) { // Get a list of all powershell files in the workspace path var allFiles = workspace.EnumeratePSFiles(); foreach (var file in allFiles) { if (filesSearched.Contains(file)) { continue; } Token[] tokens = null; ParseError[] parseErrors = null; foundDefinition = AstOperations.FindDefinitionOfSymbol( Parser.ParseFile(file, out tokens, out parseErrors), foundSymbol); filesSearched.Add(file); if (foundDefinition != null) { foundDefinition.FilePath = file; break; } } } // if definition is not found in file in the workspace // look for it in the builtin commands if (foundDefinition == null) { CommandInfo cmdInfo = await CommandHelpers.GetCommandInfo( foundSymbol.SymbolName, this.powerShellContext); foundDefinition = FindDeclarationForBuiltinCommand( cmdInfo, foundSymbol, workspace); } return(foundDefinition != null ? new GetDefinitionResult(foundDefinition) : null); }
/// <summary> /// Finds the symbol in the script given a file location /// </summary> /// <param name="scriptFile">The details and contents of a open script file</param> /// <param name="lineNumber">The line number of the cursor for the given script</param> /// <param name="columnNumber">The coulumn number of the cursor for the given script</param> /// <returns>A SymbolReference of the symbol found at the given location /// or null if there is no symbol at that location /// </returns> public SymbolReference FindSymbolAtLocation( ScriptFile scriptFile, int lineNumber, int columnNumber) { SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, columnNumber); if (symbolReference != null) { symbolReference.FilePath = scriptFile.FilePath; } return symbolReference; }
/// <summary> /// Finds the definition of a symbol in the script file or any of the /// files that it references. /// </summary> /// <param name="sourceFile">The initial script file to be searched for the symbol's definition.</param> /// <param name="foundSymbol">The symbol for which a definition will be found.</param> /// <param name="workspace">The Workspace to which the ScriptFile belongs.</param> /// <returns>The resulting GetDefinitionResult for the symbol's definition.</returns> public async Task <GetDefinitionResult> GetDefinitionOfSymbol( ScriptFile sourceFile, SymbolReference foundSymbol, Workspace workspace) { Validate.IsNotNull(nameof(sourceFile), sourceFile); Validate.IsNotNull(nameof(foundSymbol), foundSymbol); Validate.IsNotNull(nameof(workspace), workspace); ScriptFile[] referencedFiles = workspace.ExpandScriptReferences( sourceFile); var filesSearched = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // look through the referenced files until definition is found // or there are no more file to look through SymbolReference foundDefinition = null; foreach (ScriptFile scriptFile in referencedFiles) { foundDefinition = AstOperations.FindDefinitionOfSymbol( scriptFile.ScriptAst, foundSymbol); filesSearched.Add(scriptFile.FilePath); if (foundDefinition != null) { foundDefinition.FilePath = scriptFile.FilePath; break; } if (foundSymbol.SymbolType == SymbolType.Function) { // Dot-sourcing is parsed as a "Function" Symbol. string dotSourcedPath = GetDotSourcedPath(foundSymbol, workspace, scriptFile); if (scriptFile.FilePath == dotSourcedPath) { foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath); break; } } } // if the definition the not found in referenced files // look for it in all the files in the workspace if (foundDefinition == null) { // Get a list of all powershell files in the workspace path IEnumerable <string> allFiles = workspace.EnumeratePSFiles(); foreach (string file in allFiles) { if (filesSearched.Contains(file)) { continue; } Token[] tokens = null; ParseError[] parseErrors = null; foundDefinition = AstOperations.FindDefinitionOfSymbol( Parser.ParseFile(file, out tokens, out parseErrors), foundSymbol); filesSearched.Add(file); if (foundDefinition != null) { foundDefinition.FilePath = file; break; } } } // if definition is not found in file in the workspace // look for it in the builtin commands if (foundDefinition == null) { CommandInfo cmdInfo = await CommandHelpers.GetCommandInfo( foundSymbol.SymbolName, _powerShellContext); foundDefinition = FindDeclarationForBuiltinCommand( cmdInfo, foundSymbol, workspace); } return(foundDefinition != null ? new GetDefinitionResult(foundDefinition) : null); }
/// <summary> /// Finds all the symbols in a file. /// </summary> /// <param name="scriptFile">The ScriptFile in which the symbol can be located.</param> /// <returns></returns> public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile) { Validate.IsNotNull("scriptFile", scriptFile); IEnumerable<SymbolReference> symbolReferencesinFile = AstOperations .FindSymbolsInDocument(scriptFile.ScriptAst, this.powerShellContext.PowerShellVersion) .Select( reference => { reference.SourceLine = scriptFile.GetLine(reference.ScriptRegion.StartLineNumber); reference.FilePath = scriptFile.FilePath; return reference; }); return new FindOccurrencesResult { FoundOccurrences = symbolReferencesinFile }; }
/// <summary> /// Gets a path from a dot-source symbol. /// </summary> /// <param name="symbol">The symbol representing the dot-source expression.</param> /// <param name="workspace">The current workspace</param> /// <param name="scriptFile">The script file containing the symbol</param> /// <returns></returns> private static string GetDotSourcedPath(SymbolReference symbol, Workspace workspace, ScriptFile scriptFile) { string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"')); string psScriptRoot = Path.GetDirectoryName(scriptFile.FilePath); return(workspace.ResolveRelativeScriptPath(psScriptRoot, Regex.Replace(cleanedUpSymbol, @"\$PSScriptRoot|\${PSScriptRoot}", psScriptRoot, RegexOptions.IgnoreCase))); }
/// <summary> /// Finds the definition of a symbol in the script file or any of the /// files that it references. /// </summary> /// <param name="sourceFile">The initial script file to be searched for the symbol's definition.</param> /// <param name="foundSymbol">The symbol for which a definition will be found.</param> /// <param name="workspace">The Workspace to which the ScriptFile belongs.</param> /// <returns>The resulting GetDefinitionResult for the symbol's definition.</returns> public async Task<GetDefinitionResult> GetDefinitionOfSymbol( ScriptFile sourceFile, SymbolReference foundSymbol, Workspace workspace) { Validate.IsNotNull("sourceFile", sourceFile); Validate.IsNotNull("foundSymbol", foundSymbol); Validate.IsNotNull("workspace", workspace); ScriptFile[] referencedFiles = workspace.ExpandScriptReferences( sourceFile); // look through the referenced files until definition is found // or there are no more file to look through SymbolReference foundDefinition = null; for (int i = 0; i < referencedFiles.Length; i++) { foundDefinition = AstOperations.FindDefinitionOfSymbol( referencedFiles[i].ScriptAst, foundSymbol); if (foundDefinition != null) { foundDefinition.FilePath = referencedFiles[i].FilePath; break; } } // if definition is not found in referenced files // look for it in the builtin commands if (foundDefinition == null) { CommandInfo cmdInfo = await GetCommandInfo(foundSymbol.SymbolName); foundDefinition = await FindDeclarationForBuiltinCommand( cmdInfo, foundSymbol, workspace); } return foundDefinition != null ? new GetDefinitionResult(foundDefinition) : null; }
/// <summary> /// Finds a function definition that follows or contains the given line number. /// </summary> /// <param name="scriptFile">Open script file.</param> /// <param name="lineNumber">The 1 based line on which to look for function definition.</param> /// <param name="helpLocation"></param> /// <returns>If found, returns the function definition, otherwise, returns null.</returns> public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( ScriptFile scriptFile, int lineNumber, out string helpLocation) { // check if the next line contains a function definition FunctionDefinitionAst funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); if (funcDefnAst != null) { helpLocation = "before"; return(funcDefnAst); } // find all the script definitions that contain the line `lineNumber` IEnumerable <Ast> foundAsts = scriptFile.ScriptAst.FindAll( ast => { var fdAst = ast as FunctionDefinitionAst; if (fdAst == null) { return(false); } return(fdAst.Body.Extent.StartLineNumber < lineNumber && fdAst.Body.Extent.EndLineNumber > lineNumber); }, true); if (foundAsts == null || !foundAsts.Any()) { helpLocation = null; return(null); } // of all the function definitions found, return the innermost function // definition that contains `lineNumber` foreach (FunctionDefinitionAst foundAst in foundAsts.Cast <FunctionDefinitionAst>()) { if (funcDefnAst == null) { funcDefnAst = foundAst; continue; } if (funcDefnAst.Extent.StartOffset >= foundAst.Extent.StartOffset && funcDefnAst.Extent.EndOffset <= foundAst.Extent.EndOffset) { funcDefnAst = foundAst; } } // TODO use tokens to check for non empty character instead of just checking for line offset if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) { helpLocation = "begin"; return(funcDefnAst); } if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1) { helpLocation = "end"; return(funcDefnAst); } // If we didn't find a function definition, then return null helpLocation = null; return(null); }
/// <summary> /// Finds the parameter set hints of a specific command (determined by a given file location) /// </summary> /// <param name="file">The details and contents of a open script file</param> /// <param name="lineNumber">The line number of the cursor for the given script</param> /// <param name="columnNumber">The coulumn number of the cursor for the given script</param> /// <returns>ParameterSetSignatures</returns> public async Task<ParameterSetSignatures> FindParameterSetsInFile( ScriptFile file, int lineNumber, int columnNumber) { SymbolReference foundSymbol = AstOperations.FindCommandAtPosition( file.ScriptAst, lineNumber, columnNumber); if (foundSymbol != null) { CommandInfo commandInfo = await GetCommandInfo(foundSymbol.SymbolName); if (commandInfo != null) { try { IEnumerable<CommandParameterSetInfo> commandParamSets = commandInfo.ParameterSets; return new ParameterSetSignatures(commandParamSets, foundSymbol); } catch (InvalidOperationException) { // For some commands there are no paramsets (like applications). Until // the valid command types are better understood, catch this exception // which gets raised when there are no ParameterSets for the command type. return null; } } else { return null; } } else { return null; } }
/// <summary> /// Perform semantic analysis on the given ScriptFile and returns /// an array of ScriptFileMarkers. /// </summary> /// <param name="file">The ScriptFile which will be analyzed for semantic markers.</param> /// <returns>An array of ScriptFileMarkers containing semantic analysis results.</returns> public async Task <ScriptFileMarker[]> GetSemanticMarkersAsync(ScriptFile file) { return(await GetSemanticMarkersAsync <string>(file, ActiveRules, SettingsPath)); }