/// <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> internal async Task <IEnumerable <CompletionItem> > GetCompletionsInFileAsync( ScriptFile scriptFile, int lineNumber, int columnNumber, CancellationToken cancellationToken) { Validate.IsNotNull(nameof(scriptFile), scriptFile); CommandCompletion result = await AstOperations.GetCompletionsAsync( scriptFile.ScriptAst, scriptFile.ScriptTokens, scriptFile.GetOffsetAtPosition(lineNumber, columnNumber), _executionService, _logger, cancellationToken).ConfigureAwait(false); if (result.CompletionMatches.Count == 0) { return(Array.Empty <CompletionItem>()); } BufferRange replacedRange = scriptFile.GetRangeBetweenOffsets( result.ReplacementIndex, result.ReplacementIndex + result.ReplacementLength); // Create OmniSharp CompletionItems from PowerShell CompletionResults. We use a for loop // because the index is used for sorting. CompletionItem[] completionItems = new CompletionItem[result.CompletionMatches.Count]; for (int i = 0; i < result.CompletionMatches.Count; i++) { completionItems[i] = CreateCompletionItem(result.CompletionMatches[i], replacedRange, i + 1); } return(completionItems); }
public void CanFindSymbolAtPostion(int lineNumber, int columnNumber, string expectedName) { SymbolReference reference = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber); Assert.NotNull(reference); Assert.Equal(expectedName, reference.SymbolName); }
/// <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> /// <returns>The resulting GetDefinitionResult for the symbol's definition.</returns> public async Task <SymbolReference> GetDefinitionOfSymbolAsync( ScriptFile sourceFile, SymbolReference foundSymbol) { Validate.IsNotNull(nameof(sourceFile), sourceFile); Validate.IsNotNull(nameof(foundSymbol), foundSymbol); ScriptFile[] referencedFiles = _workspaceService.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, 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 = _workspaceService.EnumeratePSFiles(); foreach (string file in allFiles) { if (filesSearched.Contains(file)) { continue; } foundDefinition = AstOperations.FindDefinitionOfSymbol( Parser.ParseFile(file, out Token[] tokens, out ParseError[] parseErrors),
/// <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> GetCompletionsInFileAsync( ScriptFile scriptFile, int lineNumber, int columnNumber) { Validate.IsNotNull(nameof(scriptFile), scriptFile); // Get the offset at the specified position. This method // will also validate the given position. int fileOffset = scriptFile.GetOffsetAtPosition( lineNumber, columnNumber); CommandCompletion commandCompletion = null; using (var cts = new CancellationTokenSource(DefaultWaitTimeoutMilliseconds)) { commandCompletion = await AstOperations.GetCompletionsAsync( scriptFile.ScriptAst, scriptFile.ScriptTokens, fileOffset, _powerShellContextService, _logger, cts.Token).ConfigureAwait(false); } if (commandCompletion == null) { return(new CompletionResults()); } try { CompletionResults completionResults = CompletionResults.Create( scriptFile, commandCompletion); // save state of most recent completion _mostRecentCompletions = completionResults; _mostRecentRequestFile = scriptFile.Id; _mostRecentRequestLine = lineNumber; _mostRecentRequestOffest = columnNumber; return(completionResults); } catch (ArgumentException e) { // Bad completion results could return an invalid // replacement range, catch that here _logger.LogError( $"Caught exception while trying to create CompletionResults:\n\n{e.ToString()}"); return(new CompletionResults()); } }
/// <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 column number of the cursor for the given script</param> /// <returns>ParameterSetSignatures</returns> public async Task <ParameterSetSignatures> FindParameterSetsInFileAsync( ScriptFile file, int lineNumber, int columnNumber) { SymbolReference foundSymbol = AstOperations.FindCommandAtPosition( file.ScriptAst, lineNumber, columnNumber); // If we are not possibly looking at a Function, we don't // need to continue because we won't be able to get the // CommandInfo object. if (foundSymbol?.SymbolType is not SymbolType.Function and not SymbolType.Unknown) { return(null); } CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); if (commandInfo == null) { return(null); } try { IEnumerable <CommandParameterSetInfo> commandParamSets = commandInfo.ParameterSets; return(new ParameterSetSignatures(commandParamSets, foundSymbol)); } catch (RuntimeException e) { // A RuntimeException will be thrown when an invalid attribute is // on a parameter binding block and then that command/script has // its signatures resolved by typing it into a script. _logger.LogException("RuntimeException encountered while accessing command parameter sets", e); return(null); } 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); } }
IEnumerable <SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { if ((scriptFile.FilePath != null && scriptFile.FilePath.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) || AstOperations.IsPowerShellDataFileAst(scriptFile.ScriptAst)) { var findHashtableSymbolsVisitor = new FindHashtableSymbolsVisitor(); scriptFile.ScriptAst.Visit(findHashtableSymbolsVisitor); return(findHashtableSymbolsVisitor.SymbolReferences); } return(Enumerable.Empty <SymbolReference>()); }
/// <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> FindParameterSetsInFileAsync( ScriptFile file, int lineNumber, int columnNumber, PowerShellContextService powerShellContext) { SymbolReference foundSymbol = AstOperations.FindCommandAtPosition( file.ScriptAst, lineNumber, columnNumber); if (foundSymbol == null) { return(null); } CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, powerShellContext).ConfigureAwait(false); if (commandInfo == null) { return(null); } try { IEnumerable <CommandParameterSetInfo> commandParamSets = commandInfo.ParameterSets; return(new ParameterSetSignatures(commandParamSets, foundSymbol)); } catch (RuntimeException e) { // A RuntimeException will be thrown when an invalid attribute is // on a parameter binding block and then that command/script has // its signatures resolved by typing it into a script. _logger.LogException("RuntimeException encountered while accessing command parameter sets", e); return(null); } 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); } }
IEnumerable <SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { if (scriptFile != null && scriptFile.FilePath != null && (scriptFile.FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || scriptFile.FilePath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase))) { return (AstOperations.FindSymbolsInDocument( scriptFile.ScriptAst, this.powerShellVersion)); } return(Enumerable.Empty <SymbolReference>()); }
public void CanFindReferencesOfSymbolAtPostion(int lineNumber, int columnNumber, Position[] positions) { SymbolReference symbol = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber); IEnumerable <SymbolReference> references = AstOperations.FindReferencesOfSymbol(s_ast, symbol, needsAliases: false); int positionsIndex = 0; foreach (SymbolReference reference in references) { Assert.Equal(positions[positionsIndex].Line, reference.ScriptRegion.StartLineNumber); Assert.Equal(positions[positionsIndex].Character, reference.ScriptRegion.StartColumnNumber); positionsIndex++; } }
/// <summary> /// Finds all the occurrences 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="symbolLineNumber">The line number of the cursor for the given script</param> /// <param name="symbolColumnNumber">The column number of the cursor for the given script</param> /// <returns>FindOccurrencesResult</returns> public static IReadOnlyList <SymbolReference> FindOccurrencesInFile( ScriptFile file, int symbolLineNumber, int symbolColumnNumber) { SymbolReference foundSymbol = AstOperations.FindSymbolAtPosition( file.ScriptAst, symbolLineNumber, symbolColumnNumber); if (foundSymbol == null) { return(null); } return(AstOperations.FindReferencesOfSymbol(file.ScriptAst, foundSymbol).ToArray()); }
/// <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 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 Task <SymbolDetails> FindSymbolDetailsAtLocationAsync( ScriptFile scriptFile, int lineNumber, int columnNumber) { SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, columnNumber); if (symbolReference == null) { return(Task.FromResult <SymbolDetails>(null)); } symbolReference.FilePath = scriptFile.FilePath; return(SymbolDetails.CreateAsync( symbolReference, _runspaceContext.CurrentRunspace, _executionService)); }
/// <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> FindSymbolDetailsAtLocationAsync( ScriptFile scriptFile, int lineNumber, int columnNumber) { SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, columnNumber); if (symbolReference == null) { return(null); } symbolReference.FilePath = scriptFile.FilePath; SymbolDetails symbolDetails = await SymbolDetails.CreateAsync( symbolReference, _powerShellContextService).ConfigureAwait(false); return(symbolDetails); }
/// <summary> /// Parses the current file contents to get the AST, tokens, /// and parse errors. /// </summary> private void ParseFileContents() { #if false ParseError[] parseErrors = null; // First, get the updated file range int lineCount = this.FileLines.Count; if (lineCount > 0) { this.FileRange = new BufferRange( new BufferPosition(1, 1), new BufferPosition( lineCount + 1, this.FileLines[lineCount - 1].Length + 1)); } else { this.FileRange = BufferRange.None; } try { #if SqlToolsv5r2 // This overload appeared with Windows 10 Update 1 if (this.SqlToolsVersion.Major >= 5 && this.SqlToolsVersion.Build >= 10586) { // Include the file path so that module relative // paths are evaluated correctly this.ScriptAst = Parser.ParseInput( this.Contents, this.FilePath, out this.scriptTokens, out parseErrors); } else { this.ScriptAst = Parser.ParseInput( this.Contents, out this.scriptTokens, out parseErrors); } #else this.ScriptAst = Parser.ParseInput( this.Contents, out this.scriptTokens, out parseErrors); #endif } catch (RuntimeException ex) { var parseError = new ParseError( null, ex.ErrorRecord.FullyQualifiedErrorId, ex.Message); parseErrors = new[] { parseError }; this.scriptTokens = new Token[0]; this.ScriptAst = null; } // Translate parse errors into syntax markers this.SyntaxMarkers = parseErrors .Select(ScriptFileMarker.FromParseError) .ToArray(); //Get all dot sourced referenced files and store them this.ReferencedFiles = AstOperations.FindDotSourcedIncludes(this.ScriptAst); #endif }
/// <summary> /// Parses the current file contents to get the AST, tokens, /// and parse errors. /// </summary> private void ParseFileContents() { ParseError[] parseErrors = null; // First, get the updated file range int lineCount = this.FileLines.Count; if (lineCount > 0) { this.FileRange = new BufferRange( new BufferPosition(1, 1), new BufferPosition( lineCount + 1, this.FileLines[lineCount - 1].Length + 1)); } else { this.FileRange = BufferRange.None; } try { Token[] scriptTokens; // This overload appeared with Windows 10 Update 1 if (this.powerShellVersion.Major >= 6 || (this.powerShellVersion.Major == 5 && this.powerShellVersion.Build >= 10586)) { // Include the file path so that module relative // paths are evaluated correctly this.ScriptAst = Parser.ParseInput( this.Contents, this.FilePath, out scriptTokens, out parseErrors); } else { this.ScriptAst = Parser.ParseInput( this.Contents, out scriptTokens, out parseErrors); } this.ScriptTokens = scriptTokens; } catch (RuntimeException ex) { var parseError = new ParseError( null, ex.ErrorRecord.FullyQualifiedErrorId, ex.Message); parseErrors = new[] { parseError }; this.ScriptTokens = Array.Empty <Token>(); this.ScriptAst = null; } // Translate parse errors into syntax markers this.DiagnosticMarkers = parseErrors .Select(ScriptFileMarker.FromParseError) .ToList(); // Untitled files have no directory // Discussed in https://github.com/PowerShell/PowerShellEditorServices/pull/815. // Rather than working hard to enable things for untitled files like a phantom directory, // users should save the file. if (IsUntitledPath(this.FilePath)) { // Need to initialize the ReferencedFiles property to an empty array. this.ReferencedFiles = Array.Empty <string>(); return; } // Get all dot sourced referenced files and store them this.ReferencedFiles = AstOperations.FindDotSourcedIncludes(this.ScriptAst, Path.GetDirectoryName(this.FilePath)); }
/// <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> /// <param name="workspace">The workspace that will be searched for symbols</param> /// <returns>FindReferencesResult</returns> public async Task <List <SymbolReference> > FindReferencesOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles, WorkspaceService workspace) { if (foundSymbol == null) { return(null); } (Dictionary <string, List <string> > cmdletToAliases, Dictionary <string, string> aliasToCmdlets) = await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false); // We want to look for references first in referenced files, hence we use ordered dictionary // TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic OrderedDictionary fileMap = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? new OrderedDictionary() : new OrderedDictionary(StringComparer.OrdinalIgnoreCase); foreach (ScriptFile scriptFile in referencedFiles) { fileMap[scriptFile.FilePath] = scriptFile; } foreach (string filePath in workspace.EnumeratePSFiles()) { if (!fileMap.Contains(filePath)) { if (!workspace.TryGetFile(filePath, out ScriptFile scriptFile)) { // If we can't access the file for some reason, just ignore it continue; } fileMap[filePath] = scriptFile; } } List <SymbolReference> symbolReferences = new(); foreach (object fileName in fileMap.Keys) { ScriptFile file = (ScriptFile)fileMap[fileName]; IEnumerable <SymbolReference> references = AstOperations.FindReferencesOfSymbol( file.ScriptAst, foundSymbol, cmdletToAliases, aliasToCmdlets); foreach (SymbolReference reference in references) { try { reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); } catch (ArgumentOutOfRangeException e) { reference.SourceLine = string.Empty; _logger.LogException("Found reference is out of range in script file", e); } reference.FilePath = file.FilePath; symbolReferences.Add(reference); } } return(symbolReferences); }
/// <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> /// <param name="workspace">The workspace that will be searched for symbols</param> /// <returns>FindReferencesResult</returns> public List <SymbolReference> FindReferencesOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles, WorkspaceService workspace) { if (foundSymbol == null) { return(null); } // NOTE: we use to make sure aliases were loaded but took it out because we needed the pipeline thread. // We want to look for references first in referenced files, hence we use ordered dictionary // TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic var fileMap = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? new OrderedDictionary() : new OrderedDictionary(StringComparer.OrdinalIgnoreCase); foreach (ScriptFile scriptFile in referencedFiles) { fileMap[scriptFile.FilePath] = scriptFile; } foreach (string filePath in workspace.EnumeratePSFiles()) { if (!fileMap.Contains(filePath)) { if (!workspace.TryGetFile(filePath, out ScriptFile scriptFile)) { // If we can't access the file for some reason, just ignore it continue; } fileMap[filePath] = scriptFile; } } var symbolReferences = new List <SymbolReference>(); foreach (object fileName in fileMap.Keys) { var file = (ScriptFile)fileMap[fileName]; IEnumerable <SymbolReference> references = AstOperations.FindReferencesOfSymbol( file.ScriptAst, foundSymbol, needsAliases: false); foreach (SymbolReference reference in references) { try { reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); } catch (ArgumentOutOfRangeException e) { reference.SourceLine = string.Empty; _logger.LogException("Found reference is out of range in script file", e); } reference.FilePath = file.FilePath; symbolReferences.Add(reference); } } return(symbolReferences); }
/// <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> /// <returns>The resulting GetDefinitionResult for the symbol's definition.</returns> public async Task <SymbolReference> GetDefinitionOfSymbolAsync( ScriptFile sourceFile, SymbolReference foundSymbol) { Validate.IsNotNull(nameof(sourceFile), sourceFile); Validate.IsNotNull(nameof(foundSymbol), foundSymbol); // If symbol is an alias, resolve it. (Dictionary <string, List <string> > _, Dictionary <string, string> aliasToCmdlets) = await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false); if (aliasToCmdlets.ContainsKey(foundSymbol.SymbolName)) { foundSymbol = new SymbolReference( foundSymbol.SymbolType, aliasToCmdlets[foundSymbol.SymbolName], foundSymbol.ScriptRegion, foundSymbol.FilePath, foundSymbol.SourceLine); } ScriptFile[] referencedFiles = _workspaceService.ExpandScriptReferences( sourceFile); HashSet <string> filesSearched = new(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, 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 foreach (string file in _workspaceService.EnumeratePSFiles()) { if (filesSearched.Contains(file)) { continue; } foundDefinition = AstOperations.FindDefinitionOfSymbol( Parser.ParseFile(file, out Token[] tokens, out ParseError[] parseErrors),