diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index eb50ce2d5..5f9b6d958 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -57,14 +57,17 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ /// /// The PowerShell script file to get code lenses for. /// - /// An array of CodeLenses describing all functions in the given script file. + /// An array of CodeLenses describing all functions, classes and enums in the given script file. public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) { List acc = new(); foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { cancellationToken.ThrowIfCancellationRequested(); - if (sym.SymbolType == SymbolType.Function) + if (sym.SymbolType is + SymbolType.Function or + SymbolType.Class or + SymbolType.Enum) { acc.Add(new CodeLens { @@ -96,7 +99,7 @@ public async Task ResolveCodeLens( ScriptFile[] references = _workspaceService.ExpandScriptReferences( scriptFile); - SymbolReference foundSymbol = SymbolsService.FindFunctionDefinitionAtLocation( + SymbolReference foundSymbol = SymbolsService.FindSymbolDefinitionAtLocation( scriptFile, codeLens.Range.Start.Line + 1, codeLens.Range.Start.Character + 1); diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs index 92b33c8de..0b40a5cc1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs @@ -33,17 +33,6 @@ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( /// A collection of SymbolReference objects public static IEnumerable FindSymbolsInDocument(Ast scriptAst) { - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - // if (powerShellVersion >= new Version(5,0)) - // { - //#if PowerShell v5 - // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); - // scriptAst.Visit(findSymbolsVisitor); - // symbolReferences = findSymbolsVisitor.SymbolReferences; - //#endif - // } - // else FindSymbolsVisitor findSymbolsVisitor = new(); scriptAst.Visit(findSymbolsVisitor); return findSymbolsVisitor.SymbolReferences; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index 80a1dd8b4..84d0cef29 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -82,6 +82,29 @@ await CommandHelpers.GetCommandSynopsisAsync( symbolDetails.DisplayString = symbolReference.SymbolName; return symbolDetails; + case SymbolType.Class: + symbolDetails.DisplayString = "class " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Enum: + symbolDetails.DisplayString = "enum " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Type: + symbolDetails.DisplayString = "type " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Constructor: + case SymbolType.Method: + case SymbolType.EnumMember: + case SymbolType.Property: + symbolDetails.DisplayString = symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Configuration: + symbolDetails.DisplayString = "configuration " + symbolReference.SymbolName; + return symbolDetails; + default: return symbolDetails; } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 02778b106..7f5850f45 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -41,6 +41,41 @@ internal enum SymbolType /// /// The symbol is a hashtable key /// - HashtableKey + HashtableKey, + + /// + /// The symbol is a class + /// + Class, + + /// + /// The symbol is a enum + /// + Enum, + + /// + /// The symbol is a enum member/value + /// + EnumMember, + + /// + /// The symbol is a class property + /// + Property, + + /// + /// The symbol is a class method + /// + Method, + + /// + /// The symbol is a class constructor + /// + Constructor, + + /// + /// The symbol is a type reference + /// + Type, } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index f052520f0..294cbb986 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -288,7 +288,7 @@ public static IReadOnlyList FindOccurrencesInFile( } /// - /// Finds a function definition in the script given a file location + /// Finds a function, class or enum definition in the script given a file location /// /// The details and contents of a open script file /// The line number of the cursor for the given script @@ -296,7 +296,7 @@ public static IReadOnlyList FindOccurrencesInFile( /// A SymbolReference of the symbol found at the given location /// or null if there is no symbol at that location /// - public static SymbolReference FindFunctionDefinitionAtLocation( + public static SymbolReference FindSymbolDefinitionAtLocation( ScriptFile scriptFile, int lineNumber, int columnNumber) @@ -306,7 +306,7 @@ public static SymbolReference FindFunctionDefinitionAtLocation( scriptFile.ScriptAst, lineNumber, columnNumber, - includeFunctionDefinitions: true); + includeDefinitions: true); if (symbolReference != null) { @@ -332,7 +332,8 @@ public Task FindSymbolDetailsAtLocationAsync( AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, - columnNumber); + columnNumber, + returnFullSignature: true); if (symbolReference == null) { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index e14b2051a..e843128d2 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -147,19 +147,22 @@ await executionService.ExecuteDelegateAsync( /// The abstract syntax tree of the given script /// The line number of the cursor for the given script /// The column number of the cursor for the given script - /// Includes full function definition ranges in the search. + /// Includes full symbol definition ranges in the search. + /// Includes return or property type in symbol name. /// SymbolReference of found symbol public static SymbolReference FindSymbolAtPosition( Ast scriptAst, int lineNumber, int columnNumber, - bool includeFunctionDefinitions = false) + bool includeDefinitions = false, + bool returnFullSignature = false) { FindSymbolVisitor symbolVisitor = new( lineNumber, columnNumber, - includeFunctionDefinitions); + includeDefinitions, + returnFullSignature); scriptAst.Visit(symbolVisitor); @@ -228,18 +231,6 @@ public static SymbolReference FindDefinitionOfSymbol( /// A collection of SymbolReference objects public static IEnumerable FindSymbolsInDocument(Ast scriptAst) { - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - // if (powerShellVersion >= new Version(5,0)) - // { - //#if PowerShell v5 - // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); - // scriptAst.Visit(findSymbolsVisitor); - // symbolReferences = findSymbolsVisitor.SymbolReferences; - //#endif - // } - // else - FindSymbolsVisitor findSymbolsVisitor = new(); scriptAst.Visit(findSymbolsVisitor); return findSymbolsVisitor.SymbolReferences; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index 5c3071451..8c8e1842a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -3,13 +3,14 @@ using System; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The visitor used to find the definition of a symbol /// - internal class FindDeclarationVisitor : AstVisitor + internal class FindDeclarationVisitor : AstVisitor2 { private readonly SymbolReference symbolRef; private readonly string variableName; @@ -36,27 +37,22 @@ public FindDeclarationVisitor(SymbolReference symbolRef) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - // Get the start column number of the function name, - // instead of the the start column of 'function' and create new extent for the functionName - int startColumnNumber = - functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name, StringComparison.OrdinalIgnoreCase) + 1; - - IScriptExtent nameExtent = new ScriptExtent() + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length, - File = functionDefinitionAst.Extent.File - }; + return AstVisitAction.Continue; + } // We compare to the SymbolName instead of its text because it may have been resolved // from an alias. if (symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + functionDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { + // Get the start column number of the function name, + // instead of the the start column of 'function' and create new extent for the functionName + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); + FoundDeclaration = new SymbolReference( SymbolType.Function, @@ -68,6 +64,99 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return base.VisitFunctionDefinition(functionDefinitionAst); } + /// + /// Decides if the current type definition is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Enum or SymbolType.Class and have the same name as the symbol + /// + /// A TypeDefinitionAst in the script's AST + /// A decision to stop searching if the right TypeDefinitionAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + if ((symbolRef.SymbolType is SymbolType.Type || symbolRef.SymbolType.Equals(symbolType)) && + typeDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // We only want the type name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); + + FoundDeclaration = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Decides if the current function member is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Constructor or SymbolType.Method and have the same name as the symbol + /// + /// A FunctionMemberAst in the script's AST + /// A decision to stop searching if the right FunctionMemberAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + if (symbolRef.SymbolType.Equals(symbolType) && + VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); + + FoundDeclaration = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Decides if the current property member is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Property or SymbolType.EnumMember and have the same name as the symbol + /// + /// A PropertyMemberAst in the script's AST + /// A decision to stop searching if the right PropertyMemberAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + if (symbolRef.SymbolType.Equals(symbolType) && + VisitorUtils.GetMemberOverloadName(propertyMemberAst, false).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); + + FoundDeclaration = + new SymbolReference( + SymbolType.Property, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + /// /// Check if the left hand side of an assignmentStatementAst is a VariableExpressionAst /// with the same name as that of symbolRef. diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 44b64c8f5..7f6014a2a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// /// The visitor used to find the references of a symbol in a script's AST /// - internal class FindReferencesVisitor : AstVisitor + internal class FindReferencesVisitor : AstVisitor2 { private readonly SymbolReference _symbolRef; private readonly IDictionary> _cmdletToAliasDictionary; @@ -113,35 +113,32 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// Decides if the current function definition is a reference of the symbol being searched for. /// A reference of the symbol will be a of type SymbolType.Function and have the same name as the symbol /// - /// A functionDefinitionAst in the script's AST + /// A FunctionDefinitionAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - (int startColumnNumber, int startLineNumber) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); - - IScriptExtent nameExtent = new ScriptExtent() + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) { - Text = functionDefinitionAst.Name, - StartLineNumber = startLineNumber, - EndLineNumber = startLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length, - File = functionDefinitionAst.Extent.File - }; + return AstVisitAction.Continue; + } if (_symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + functionDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { + // We only want the function name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); FoundReferences.Add(new SymbolReference(SymbolType.Function, nameExtent)); } return base.VisitFunctionDefinition(functionDefinitionAst); } /// - /// Decides if the current function definition is a reference of the symbol being searched for. + /// Decides if the current command parameter is a reference of the symbol being searched for. /// A reference of the symbol will be a of type SymbolType.Parameter and have the same name as the symbol /// - /// A commandParameterAst in the script's AST + /// A CommandParameterAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) { @@ -154,10 +151,10 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command } /// - /// Decides if the current function definition is a reference of the symbol being searched for. + /// Decides if the current variable expression is a reference of the symbol being searched for. /// A reference of the symbol will be a of type SymbolType.Variable and have the same name as the symbol /// - /// A variableExpressionAst in the script's AST + /// A VariableExpressionAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { @@ -168,5 +165,112 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var } return AstVisitAction.Continue; } + + /// + /// Decides if the current type definition is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Class or SymbolType.Enum and have the same name as the symbol + /// + /// A TypeDefinitionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + if ((_symbolRef.SymbolType is SymbolType.Type || _symbolRef.SymbolType.Equals(symbolType)) && + typeDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // We only want the type name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); + FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current type expression is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Type and have the same name as the symbol + /// + /// A TypeExpressionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + // We don't know if we're looking at a class or enum, but name is likely unique + if (IsTypeSymbol(_symbolRef.SymbolType) && + typeExpressionAst.TypeName.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + FoundReferences.Add(new SymbolReference(SymbolType.Type, typeExpressionAst.Extent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current type constraint is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Type and have the same name as the symbol + /// + /// A TypeConstraintAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + // We don't know if we're looking at a class or enum, but name is likely unique + if (IsTypeSymbol(_symbolRef.SymbolType) && + typeConstraintAst.TypeName.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + FoundReferences.Add(new SymbolReference(SymbolType.Type, typeConstraintAst.Extent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current function member is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Constructor or SymbolType.Method and have the same name as the symbol + /// + /// A FunctionMemberAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + if (_symbolRef.SymbolType.Equals(symbolType) && + VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); + FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current property member is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Property or SymbolType.EnumMember + /// and have the same name as the symbol. + /// + /// A PropertyMemberAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + if (_symbolRef.SymbolType.Equals(symbolType) && + VisitorUtils.GetMemberOverloadName(propertyMemberAst, false).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); + FoundReferences.Add(new SymbolReference(SymbolType.Property, nameExtent)); + } + return AstVisitAction.Continue; + } + + /// + /// Tests if symbol type is a type (class/enum) definition or type reference. + /// + private static bool IsTypeSymbol(SymbolType symbolType) + => symbolType is SymbolType.Class or SymbolType.Enum or SymbolType.Type; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index bf2520a3c..b45ee03eb 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -9,22 +9,25 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// /// The visitor used to find the symbol at a specific location in the AST /// - internal class FindSymbolVisitor : AstVisitor + internal class FindSymbolVisitor : AstVisitor2 { private readonly int lineNumber; private readonly int columnNumber; - private readonly bool includeFunctionDefinitions; + private readonly bool includeDefinitions; + private readonly bool returnFullSignature; public SymbolReference FoundSymbolReference { get; private set; } public FindSymbolVisitor( int lineNumber, int columnNumber, - bool includeFunctionDefinitions) + bool includeDefinitions, + bool returnFullSignature) { this.lineNumber = lineNumber; this.columnNumber = columnNumber; - this.includeFunctionDefinitions = includeFunctionDefinitions; + this.includeDefinitions = includeDefinitions; + this.returnFullSignature = returnFullSignature; } /// @@ -58,32 +61,34 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - int startLineNumber = functionDefinitionAst.Extent.StartLineNumber; - int startColumnNumber = functionDefinitionAst.Extent.StartColumnNumber; - int endLineNumber = functionDefinitionAst.Extent.EndLineNumber; - int endColumnNumber = functionDefinitionAst.Extent.EndColumnNumber; - - if (!includeFunctionDefinitions) + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) { - // We only want the function name - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); - startLineNumber = startLine; - startColumnNumber = startColumn; - endLineNumber = startLine; - endColumnNumber = startColumn + functionDefinitionAst.Name.Length; + return AstVisitAction.Continue; } - IScriptExtent nameExtent = new ScriptExtent() + IScriptExtent nameExtent; + + if (includeDefinitions) { - Text = functionDefinitionAst.Name, - StartLineNumber = startLineNumber, - EndLineNumber = endLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = endColumnNumber, - File = functionDefinitionAst.Extent.File - }; + nameExtent = new ScriptExtent() + { + Text = functionDefinitionAst.Name, + StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, + EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, + StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber, + EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber, + File = functionDefinitionAst.Extent.File + }; + } + else + { + // We only want the function name + nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); + } - if (IsPositionInExtent(nameExtent)) + if (nameExtent.Contains(lineNumber, columnNumber)) { FoundSymbolReference = new SymbolReference( @@ -116,7 +121,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command } /// - /// Checks to see if this variable expression is the symbol we are looking for. + /// Checks to see if this variable expression is the symbol we are looking for. /// /// A VariableExpressionAst object in the script's AST /// A decision to stop searching if the right symbol was found, @@ -137,7 +142,9 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var } /// - /// Is the position of the given location is in the ast's extent + /// Is the position of the given location is in the ast's extent. + /// Only works with single-line extents like name extents. + /// Use extension for definition extents. /// /// The script extent of the element /// True if the given position is in the range of the element's extent @@ -147,5 +154,197 @@ private bool IsPositionInExtent(IScriptExtent extent) extent.StartColumnNumber <= columnNumber && extent.EndColumnNumber >= columnNumber; } + + /// + /// Checks to see if this function member is the symbol we are looking for. + /// + /// A FunctionMemberAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, returnFullSignature); + + if (IsPositionInExtent(nameExtent)) + { + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + FoundSymbolReference = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this type definition is the symbol we are looking for. + /// + /// A TypeDefinitionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + IScriptExtent nameExtent; + + if (includeDefinitions) + { + nameExtent = new ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, + EndLineNumber = typeDefinitionAst.Extent.EndLineNumber, + StartColumnNumber = typeDefinitionAst.Extent.StartColumnNumber, + EndColumnNumber = typeDefinitionAst.Extent.EndColumnNumber, + File = typeDefinitionAst.Extent.File + }; + } + else + { + // We only want the type name + nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); + } + + if (nameExtent.Contains(lineNumber, columnNumber)) + { + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + FoundSymbolReference = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this type expression is the symbol we are looking for. + /// + /// A TypeExpressionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + // Show only type name (skip leading '['). Offset by StartColumn to include indentation etc. + int startColumnNumber = typeExpressionAst.Extent.StartColumnNumber + 1; + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeExpressionAst.TypeName.Name, + StartLineNumber = typeExpressionAst.Extent.StartLineNumber, + EndLineNumber = typeExpressionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeExpressionAst.TypeName.Name.Length, + File = typeExpressionAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Type, + nameExtent); + return AstVisitAction.StopVisit; + } + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this type constraint is the symbol we are looking for. + /// + /// A TypeConstraintAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + // Show only type name (skip leading '[' if present). It's not present for inherited types + // Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeConstraintAst.Extent.Text[0] == '[' ? + typeConstraintAst.Extent.StartColumnNumber + 1 : typeConstraintAst.Extent.StartColumnNumber; + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeConstraintAst.TypeName.Name, + StartLineNumber = typeConstraintAst.Extent.StartLineNumber, + EndLineNumber = typeConstraintAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeConstraintAst.TypeName.Name.Length, + File = typeConstraintAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Type, + nameExtent); + return AstVisitAction.StopVisit; + } + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this configuration definition is the symbol we are looking for. + /// + /// A ConfigurationDefinitionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + // We only want the configuration name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); + + if (IsPositionInExtent(nameExtent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Configuration, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this property member is the symbol we are looking for. + /// + /// A PropertyMemberAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnFullSignature); + + if (IsPositionInExtent(nameExtent)) + { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + FoundSymbolReference = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index 970acb4e7..55c90b4fd 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -1,40 +1,37 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.PowerShell.EditorServices.Utility; using System.Collections.Generic; using System.Management.Automation.Language; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// - /// The visitor used to find all the symbols (function and class defs) in the AST. + /// The visitor used to find all the symbols (variables, functions and class defs etc) in the AST. /// - /// - /// Requires PowerShell v3 or higher - /// - internal class FindSymbolsVisitor : AstVisitor + internal class FindSymbolsVisitor : AstVisitor2 { public List SymbolReferences { get; } public FindSymbolsVisitor() => SymbolReferences = new List(); /// - /// Adds each function definition as a + /// Adds each function definition to symbol reference list /// - /// A functionDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found + /// A FunctionDefinitionAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - IScriptExtent nameExtent = new ScriptExtent() + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber, - File = functionDefinitionAst.Extent.File - }; + return AstVisitAction.Continue; + } + + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionDefinitionAst); + IScriptExtent nameExtent = GetNewExtent(functionDefinitionAst, functionDefinitionAst.Name, startLine, startColumn); SymbolType symbolType = functionDefinitionAst.IsWorkflow ? @@ -49,11 +46,10 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun } /// - /// Checks to see if this variable expression is the symbol we are looking for. + /// Adds each script scoped variable assignment to symbol reference list /// - /// A VariableExpressionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found + /// A VariableExpressionAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { if (!IsAssignedAtScriptScope(variableExpressionAst)) @@ -80,6 +76,107 @@ private static bool IsAssignedAtScriptScope(VariableExpressionAst variableExpres parent = parent.Parent; return parent is null || parent.Parent is null || parent.Parent.Parent is null; } + + /// + /// Adds class and enum AST to symbol reference list + /// + /// A TypeDefinitionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(typeDefinitionAst); + IScriptExtent nameExtent = GetNewExtent(typeDefinitionAst, typeDefinitionAst.Name, startLine, startColumn); + + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + SymbolReferences.Add( + new SymbolReference( + symbolType, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Adds class method and constructor AST to symbol reference list + /// + /// A FunctionMemberAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionMemberAst); + IScriptExtent nameExtent = GetNewExtent(functionMemberAst, VisitorUtils.GetMemberOverloadName(functionMemberAst, false, false), startLine, startColumn); + + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + SymbolReferences.Add( + new SymbolReference( + symbolType, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Adds class property AST to symbol reference list + /// + /// A PropertyMemberAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + bool isEnumMember = symbolType.Equals(SymbolType.EnumMember); + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); + IScriptExtent nameExtent = GetNewExtent(propertyMemberAst, propertyMemberAst.Name, startLine, startColumn); + + SymbolReferences.Add( + new SymbolReference( + symbolType, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Adds DSC configuration AST to symbol reference list + /// + /// A ConfigurationDefinitionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(configurationDefinitionAst); + IScriptExtent nameExtent = GetNewExtent(configurationDefinitionAst, configurationDefinitionAst.InstanceName.Extent.Text, startLine, startColumn); + + SymbolReferences.Add( + new SymbolReference( + SymbolType.Configuration, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Gets a new ScriptExtent for a given Ast with same range but modified Text + /// + private static ScriptExtent GetNewExtent(Ast ast, string text, int startLine, int startColumn) + { + return new ScriptExtent() + { + Text = text, + StartLineNumber = startLine, + EndLineNumber = ast.Extent.EndLineNumber, + StartColumnNumber = startColumn, + EndColumnNumber = ast.Extent.EndColumnNumber, + File = ast.Extent.File + }; + } } /// @@ -100,6 +197,8 @@ internal class FindHashtableSymbolsVisitor : AstVisitor /// /// Adds keys in the input hashtable to the symbol reference /// + /// A HashtableAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) { if (hashtableAst.KeyValuePairs == null) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs deleted file mode 100644 index 15f5e49db..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - - ///// - ///// The visitor used to find all the symbols (function and class defs) in the AST. - ///// - ///// - ///// Requires PowerShell v5 or higher - ///// - ///// - //internal class FindSymbolsVisitor2 : AstVisitor2 - //{ - // private FindSymbolsVisitor findSymbolsVisitor; - - // public List SymbolReferences - // { - // get - // { - // return this.findSymbolsVisitor.SymbolReferences; - // } - // } - - // public FindSymbolsVisitor2() - // { - // this.findSymbolsVisitor = new FindSymbolsVisitor(); - // } - - // /// - // /// Adds each function definition as a - // /// - // /// A functionDefinitionAst object in the script's AST - // /// A decision to stop searching if the right symbol was found, - // /// or a decision to continue if it wasn't found - // public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - // { - // return this.findSymbolsVisitor.VisitFunctionDefinition(functionDefinitionAst); - // } - - // /// - // /// Checks to see if this variable expression is the symbol we are looking for. - // /// - // /// A VariableExpressionAst object in the script's AST - // /// A decision to stop searching if the right symbol was found, - // /// or a decision to continue if it wasn't found - // public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - // { - // return this.findSymbolsVisitor.VisitVariableExpression(variableExpressionAst); - // } - - // public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - // { - // IScriptExtent nameExtent = new ScriptExtent() - // { - // Text = configurationDefinitionAst.InstanceName.Extent.Text, - // StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - // EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber, - // StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber, - // EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber - // }; - - // this.findSymbolsVisitor.SymbolReferences.Add( - // new SymbolReference( - // SymbolType.Configuration, - // nameExtent)); - - // return AstVisitAction.Continue; - // } - //} -} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index f0973a7f0..5071cc745 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -128,7 +128,13 @@ private static SymbolKind GetSymbolKind(SymbolType symbolType) { return symbolType switch { - SymbolType.Configuration or SymbolType.Function or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Enum => SymbolKind.Enum, + SymbolType.Class => SymbolKind.Class, + SymbolType.Constructor => SymbolKind.Constructor, + SymbolType.Method => SymbolKind.Method, + SymbolType.Property => SymbolKind.Property, + SymbolType.EnumMember => SymbolKind.EnumMember, _ => SymbolKind.Variable, }; } @@ -137,8 +143,15 @@ private static string GetDecoratedSymbolName(ISymbolReference symbolReference) { string name = symbolReference.SymbolName; - if (symbolReference.SymbolType is SymbolType.Configuration or + // Append { } for symbols with scriptblock + // Constructors and Methods have overloaded names already + if (symbolReference.SymbolType is SymbolType.Function or + SymbolType.Enum or + SymbolType.Class or + SymbolType.Constructor or + SymbolType.Method or + SymbolType.Configuration or SymbolType.Workflow) { name += " { }"; diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 8a9aaa815..3fa830f99 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -72,7 +72,7 @@ public override async Task> Handle(WorkspaceSymbolP symbols.Add(new SymbolInformation { ContainerName = containerName, - Kind = foundOccurrence.SymbolType == SymbolType.Variable ? SymbolKind.Variable : SymbolKind.Function, + Kind = GetSymbolKind(foundOccurrence.SymbolType), Location = location, Name = GetDecoratedSymbolName(foundOccurrence) }); @@ -107,8 +107,15 @@ private static string GetDecoratedSymbolName(SymbolReference symbolReference) { string name = symbolReference.SymbolName; - if (symbolReference.SymbolType is SymbolType.Configuration or + // Append { } for symbols with scriptblock + // Constructors and Methods have overloaded names already + if (symbolReference.SymbolType is SymbolType.Function or + SymbolType.Enum or + SymbolType.Class or + SymbolType.Constructor or + SymbolType.Method or + SymbolType.Configuration or SymbolType.Workflow) { name += " { }"; @@ -117,6 +124,20 @@ SymbolType.Function or return name; } + private static SymbolKind GetSymbolKind(SymbolType symbolType) + { + return symbolType switch + { + SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Enum => SymbolKind.Enum, + SymbolType.Class => SymbolKind.Class, + SymbolType.Constructor => SymbolKind.Constructor, + SymbolType.Method => SymbolKind.Method, + SymbolType.Property => SymbolKind.Property, + _ => SymbolKind.Variable, + }; + } + #endregion } } diff --git a/src/PowerShellEditorServices/Utility/Extensions.cs b/src/PowerShellEditorServices/Utility/Extensions.cs index c280f1b14..22148e8b3 100644 --- a/src/PowerShellEditorServices/Utility/Extensions.cs +++ b/src/PowerShellEditorServices/Utility/Extensions.cs @@ -135,7 +135,14 @@ public static bool Contains(this IScriptExtent scriptExtent, int line, int colum if (scriptExtent.StartLineNumber == line) { - return scriptExtent.StartColumnNumber <= column; + if (scriptExtent.StartLineNumber == scriptExtent.EndLineNumber) + { + return scriptExtent.StartColumnNumber <= column && scriptExtent.EndColumnNumber >= column; + } + else + { + return scriptExtent.StartColumnNumber <= column; + } } if (scriptExtent.EndLineNumber == line) diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 861d04acc..3fc3f7ea1 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -1,7 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Collections.Generic; using System.Management.Automation.Language; +using System.Text; +using PSESSymbols = Microsoft.PowerShell.EditorServices.Services.Symbols; namespace Microsoft.PowerShell.EditorServices.Utility { @@ -11,41 +15,276 @@ namespace Microsoft.PowerShell.EditorServices.Utility internal static class VisitorUtils { /// - /// Calculates the start line and column of the actual function name in a function definition AST. + /// Calculates the start line and column of the actual symbol name in a AST. /// - /// A FunctionDefinitionAst object in the script's AST - /// A tuple with start column and line for the function name - internal static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast) + /// An Ast object in the script's AST + /// An int specifying start index of name in the AST's extent text + /// A tuple with start column and line of the symbol name + private static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(Ast ast, int nameStartIndex) { int startColumnNumber = ast.Extent.StartColumnNumber; int startLineNumber = ast.Extent.StartLineNumber; - int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length; string astText = ast.Extent.Text; - // The line offset represents the offset on the line that we're on where as // astOffset is the offset on the entire text of the AST. - int lineOffset = astOffset; - for (; astOffset < astText.Length; astOffset++, lineOffset++) + for (int astOffset = 0; astOffset <= ast.Extent.Text.Length; astOffset++, startColumnNumber++) { if (astText[astOffset] == '\n') { // reset numbers since we are operating on a different line and increment the line number. startColumnNumber = 0; startLineNumber++; - lineOffset = 0; } else if (astText[astOffset] == '\r') { // Do nothing with carriage returns... we only look for line feeds since those // are used on every platform. } - else if (!char.IsWhiteSpace(astText[astOffset])) + else if (astOffset >= nameStartIndex && !char.IsWhiteSpace(astText[astOffset])) { // This is the start of the function name so we've found our start column and line number. break; } } - return (startColumnNumber + lineOffset, startLineNumber); + return (startColumnNumber, startLineNumber); + } + + /// + /// Calculates the start line and column of the actual function name in a function definition AST. + /// + /// A FunctionDefinitionAst object in the script's AST + /// A tuple with start column and line for the function name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionDefinitionAst functionDefinitionAst) + { + int startOffset = functionDefinitionAst.IsFilter ? "filter".Length : functionDefinitionAst.IsWorkflow ? "workflow".Length : "function".Length; + return GetNameStartColumnAndLineFromAst(functionDefinitionAst, startOffset); + } + + /// + /// Calculates the start line and column of the actual class/enum name in a type definition AST. + /// + /// A TypeDefinitionAst object in the script's AST + /// A tuple with start column and line for the type name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(TypeDefinitionAst typeDefinitionAst) + { + int startOffset = typeDefinitionAst.IsEnum ? "enum".Length : "class".Length; + return GetNameStartColumnAndLineFromAst(typeDefinitionAst, startOffset); + } + + /// + /// Calculates the start line and column of the actual method/constructor name in a function member AST. + /// + /// A FunctionMemberAst object in the script's AST + /// A tuple with start column and line for the method/constructor name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionMemberAst functionMemberAst) + { + // find name index to get offset even with attributes, static, hidden ++ + int nameStartIndex = functionMemberAst.Extent.Text.LastIndexOf(string.Concat(functionMemberAst.Name, '('), StringComparison.OrdinalIgnoreCase); + return GetNameStartColumnAndLineFromAst(functionMemberAst, nameStartIndex); + } + + /// + /// Calculates the start line and column of the actual property name in a property member AST. + /// + /// A PropertyMemberAst object in the script's AST + /// A bool indicating this is a enum member + /// A tuple with start column and line for the property name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(PropertyMemberAst propertyMemberAst, bool isEnumMember) + { + // find name index to get offset even with attributes, static, hidden ++ + string searchString = isEnumMember ? propertyMemberAst.Name : string.Concat('$', propertyMemberAst.Name); + int nameStartIndex = propertyMemberAst.Extent.Text.LastIndexOf(searchString, StringComparison.OrdinalIgnoreCase); + return GetNameStartColumnAndLineFromAst(propertyMemberAst, nameStartIndex); + } + + /// + /// Calculates the start line and column of the actual configuration name in a configuration definition AST. + /// + /// A ConfigurationDefinitionAst object in the script's AST + /// A tuple with start column and line for the configuration name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(ConfigurationDefinitionAst configurationDefinitionAst) + { + const int startOffset = 13; // "configuration".Length + return GetNameStartColumnAndLineFromAst(configurationDefinitionAst, startOffset); + } + + /// + /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) + /// + /// A FunctionDefinitionAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionDefinitionAst functionDefinitionAst) + { + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionDefinitionAst); + + return new PSESSymbols.ScriptExtent() + { + Text = functionDefinitionAst.Name, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + functionDefinitionAst.Name.Length, + File = functionDefinitionAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) + /// + /// A TypeDefinitionAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDefinitionAst) + { + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(typeDefinitionAst); + + return new PSESSymbols.ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + typeDefinitionAst.Name.Length, + File = typeDefinitionAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) + /// + /// A FunctionMemberAst in the script's AST + /// A bool indicating if class/enum name should be prepended + /// A bool indicating if return type should be included for methods + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst, bool useQualifiedName = true, bool includeReturnType = false) + { + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionMemberAst); + + return new PSESSymbols.ScriptExtent() + { + Text = GetMemberOverloadName(functionMemberAst, useQualifiedName, includeReturnType), + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + functionMemberAst.Name.Length, + File = functionMemberAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the property name only + /// + /// A PropertyMemberAst in the script's AST + /// A bool indicating if type should be included for class property + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst, bool includePropertyType = false) + { + bool isEnumMember = propertyMemberAst.Parent is TypeDefinitionAst typeDef && typeDef.IsEnum; + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); + + // +1 when class property to as start includes $ + int endColumnNumber = isEnumMember ? + startColumn + propertyMemberAst.Name.Length : + startColumn + propertyMemberAst.Name.Length + 1; + + return new PSESSymbols.ScriptExtent() + { + Text = GetMemberOverloadName(propertyMemberAst, includePropertyType), + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = endColumnNumber, + File = propertyMemberAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the configuration instance name only + /// + /// A ConfigurationDefinitionAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAst configurationDefinitionAst) + { + string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(configurationDefinitionAst); + + return new PSESSymbols.ScriptExtent() + { + Text = configurationName, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + configurationName.Length, + File = configurationDefinitionAst.Extent.File + }; + } + + /// + /// Gets the method or constructor name with parameters for current overload. + /// + /// A FunctionMemberAst object in the script's AST + /// A bool indicating if class/enum name should be prepended + /// A bool indicating if return type should be included for methods + /// Function member name with return type (optional) and parameters + internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst, + bool useQualifiedName = true, + bool includeReturnType = false) + { + StringBuilder sb = new(); + + // Prepend return type and class. Used for symbol details (hover) + if (includeReturnType && !functionMemberAst.IsConstructor) + { + sb.Append(functionMemberAst.ReturnType?.TypeName.Name ?? "void").Append(' '); + } + + if (useQualifiedName && functionMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsClass) + { + sb.Append(typeAst.Name).Append('.'); + } + + sb.Append(functionMemberAst.Name); + + // Add parameters + sb.Append('('); + if (functionMemberAst.Parameters.Count > 0) + { + List parameters = new(functionMemberAst.Parameters.Count); + foreach (ParameterAst param in functionMemberAst.Parameters) + { + parameters.Add(param.Extent.Text); + } + + sb.Append(string.Join(", ", parameters)); + } + sb.Append(')'); + + return sb.ToString(); + } + + /// + /// Gets the property name with type and class/enum. + /// + /// A PropertyMemberAst object in the script's AST + /// A bool indicating if type should be included for class property + /// Property name with type (optional) and class/enum + internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst, + bool includePropertyType = false) + { + StringBuilder sb = new(); + + // Prepend return type and class. Used for symbol details (hover) + if (propertyMemberAst.Parent is TypeDefinitionAst typeAst) + { + if (includePropertyType && !typeAst.IsEnum) + { + sb.Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object").Append(' '); + } + + sb.Append(typeAst.Name).Append('.'); + } + + sb.Append(propertyMemberAst.Name); + return sb.ToString(); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 0ca4bb2e3..d990ebe09 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -442,7 +442,7 @@ await PsesLanguageClient Range range = symInfoOrDocSym.SymbolInformation.Location.Range; Assert.Equal(1, range.Start.Line); - Assert.Equal(0, range.Start.Character); + Assert.Equal(9, range.Start.Character); Assert.Equal(3, range.End.Line); Assert.Equal(1, range.End.Character); }); @@ -841,7 +841,7 @@ public async Task NoMessageIfPesterCodeLensDisabled() } [Fact] - public async Task CanSendReferencesCodeLensRequestAsync() + public async Task CanSendFunctionReferencesCodeLensRequestAsync() { string filePath = NewTestFile(@" function CanSendReferencesCodeLensRequest { @@ -867,7 +867,7 @@ function CanSendReferencesCodeLensRequest { Range range = codeLens.Range; Assert.Equal(1, range.Start.Line); - Assert.Equal(0, range.Start.Character); + Assert.Equal(9, range.Start.Character); Assert.Equal(3, range.End.Line); Assert.Equal(1, range.End.Character); @@ -878,6 +878,110 @@ function CanSendReferencesCodeLensRequest { Assert.Equal("1 reference", codeLensResolveResult.Command.Title); } + [Fact] + public async Task CanSendClassReferencesCodeLensRequestAsync() + { + string filePath = NewTestFile(@" +param( + [MyBaseClass]$enumValue +) + +class MyBaseClass { + +} + +class ChildClass : MyBaseClass, System.IDisposable { + +} + +$o = [MyBaseClass]::new() +$o -is [MyBaseClass] +"); + + CodeLensContainer codeLenses = await PsesLanguageClient + .SendRequest( + "textDocument/codeLens", + new CodeLensParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + } + }) + .Returning(CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(codeLenses, + codeLens => + { + Range range = codeLens.Range; + Assert.Equal(5, range.Start.Line); + Assert.Equal(6, range.Start.Character); + Assert.Equal(7, range.End.Line); + Assert.Equal(1, range.End.Character); + }, + codeLens => + { + Range range = codeLens.Range; + Assert.Equal(9, range.Start.Line); + Assert.Equal(6, range.Start.Character); + Assert.Equal(11, range.End.Line); + Assert.Equal(1, range.End.Character); + } + ); + + CodeLens baseClassCodeLens = codeLenses.First(); + CodeLens codeLensResolveResult = await PsesLanguageClient + .SendRequest("codeLens/resolve", baseClassCodeLens) + .Returning(CancellationToken.None).ConfigureAwait(true); + + Assert.Equal("4 references", codeLensResolveResult.Command.Title); + } + + [Fact] + public async Task CanSendEnumReferencesCodeLensRequestAsync() + { + string filePath = NewTestFile(@" +param( + [MyEnum]$enumValue +) + +enum MyEnum { + First = 1 + Second + Third +} + +[MyEnum]::First +'First' -is [MyEnum] +"); + + CodeLensContainer codeLenses = await PsesLanguageClient + .SendRequest( + "textDocument/codeLens", + new CodeLensParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + } + }) + .Returning(CancellationToken.None).ConfigureAwait(true); + + CodeLens codeLens = Assert.Single(codeLenses); + + Range range = codeLens.Range; + Assert.Equal(5, range.Start.Line); + Assert.Equal(5, range.Start.Character); + Assert.Equal(9, range.End.Line); + Assert.Equal(1, range.End.Character); + + CodeLens codeLensResolveResult = await PsesLanguageClient + .SendRequest("codeLens/resolve", codeLens) + .Returning(CancellationToken.None).ConfigureAwait(true); + + Assert.Equal("3 references", codeLensResolveResult.Command.Title); + } + [SkippableFact] public async Task CanSendCodeActionRequestAsync() { diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs new file mode 100644 index 000000000..a0b5ae61c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition +{ + public static class FindsTypeSymbolsDefinitionData + { + public static readonly ScriptRegion ClassSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 14, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 39, + startColumnNumber: 10, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeExpressionSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 45, + startColumnNumber: 5, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeConstraintSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 25, + startColumnNumber: 24, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 9, + startColumnNumber: 14, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 19, + startColumnNumber: 25, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 15, + startColumnNumber: 32, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 41, + startColumnNumber: 11, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs new file mode 100644 index 000000000..7c012bc7c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences +{ + public static class FindsOccurrencesOnTypeSymbolsData + { + public static readonly ScriptRegion ClassSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 16, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 39, + startColumnNumber: 7, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeExpressionSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 34, + startColumnNumber: 16, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeConstraintSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 24, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 13, + startColumnNumber: 14, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 28, + startColumnNumber: 22, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 15, + startColumnNumber: 18, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 40, + startColumnNumber: 6, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs new file mode 100644 index 000000000..c01db0591 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences +{ + public static class FindsOccurrencesOnVariableData + { + public static readonly ScriptRegion SourceDetails = new( + file: TestUtilities.NormalizePath("References/SimpleFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 3, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs new file mode 100644 index 000000000..d9cbcf434 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.References +{ + public static class FindsReferencesOnTypeSymbolsData + { + public static readonly ScriptRegion ClassSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 12, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 39, + startColumnNumber: 8, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 9, + startColumnNumber: 8, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 19, + startColumnNumber: 20, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 17, + startColumnNumber: 15, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 41, + startColumnNumber: 8, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeExpressionSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 34, + startColumnNumber: 12, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeConstraintSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 25, + startColumnNumber: 22, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 new file mode 100644 index 000000000..4fe54505d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 @@ -0,0 +1,46 @@ +Get-ChildItem ./file1.ps1 +$myScriptVar = 123 + +class BaseClass { + +} + +class SuperClass : BaseClass { + SuperClass([string]$name) { + + } + + SuperClass() { } + + [string]$SomePropWithDefault = 'this is a default value' + + [int]$SomeProp + + [string]MyClassMethod([string]$param1, $param2, [int]$param3) { + $this.SomePropWithDefault = 'something happend' + return 'finished' + } + + [string] + MyClassMethod([MyEnum]$param1) { + return 'hello world' + } + [string]MyClassMethod() { + return 'hello world' + } +} + +New-Object SuperClass +$o = [SuperClass]::new() +$o.SomeProp +$o.MyClassMeth + + +enum MyEnum { + First + Second + Third +} + +[MyEnum]::First +'First' -is [MyEnum] diff --git a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs new file mode 100644 index 000000000..ff78a2c5a --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails +{ + public static class FindsDetailsForTypeSymbolsData + { + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 20, + startColumnNumber: 6, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 6, + startColumnNumber: 18, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 2, + startColumnNumber: 11, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 10, + startColumnNumber: 20, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 new file mode 100644 index 000000000..fd4a10a46 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 @@ -0,0 +1,23 @@ +class SuperClass { + SuperClass([string]$name) { + + } + + [string]$SomePropWithDefault = 'this is a default value' + + [int]$SomeProp + + [string]MyClassMethod([string]$param1, $param2, [int]$param3) { + $this.SomePropWithDefault = 'something happend' + return 'finished' + } +} + +New-Object SuperClass +$o = [SuperClass]::new() + +enum MyEnum { + First + Second + Third +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 new file mode 100644 index 000000000..defec6863 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 @@ -0,0 +1,4 @@ +# This file represents a script with a DSC configuration +configuration AConfiguration { + Node "TEST-PC" {} +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs new file mode 100644 index 000000000..6e3d45ff2 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public static class FindSymbolsInDSCFile + { + public static readonly ScriptRegion SourceDetails = + new( + file: TestUtilities.NormalizePath("Symbols/DSCFile.ps1"), + text: string.Empty, + startLineNumber: 0, + startColumnNumber: 0, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs new file mode 100644 index 000000000..0be43f8d1 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public static class FindSymbolsInNewLineSymbolFile + { + public static readonly ScriptRegion SourceDetails = + new( + file: TestUtilities.NormalizePath("Symbols/NewLineSymbols.ps1"), + text: string.Empty, + startLineNumber: 0, + startColumnNumber: 0, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 index f234fed03..db53a6c1a 100644 --- a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 @@ -22,10 +22,22 @@ function AnAdvancedFunction { workflow AWorkflow {} -Configuration AConfiguration { - Node "TEST-PC" {} +class AClass { + [string]$AProperty + + AClass([string]$AParameter) { + + } + + [void]AMethod([string]$param1, [int]$param2, $param3) { + + } +} + +enum AEnum { + AValue = 0 } AFunction 1..3 | AFilter -AnAdvancedFunction \ No newline at end of file +AnAdvancedFunction diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 new file mode 100644 index 000000000..5ca44f02a --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 @@ -0,0 +1,28 @@ +function +returnTrue { + $true +} + +class +NewLineClass { + NewLineClass() { + + } + + static + hidden + [string] + $SomePropWithDefault = 'some value' + + static + hidden + [string] + MyClassMethod([MyNewLineEnum]$param1) { + return 'hello world $param1' + } +} + +enum +MyNewLineEnum { + First +} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index e7938c287..0f30eccde 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Management.Automation; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; @@ -32,6 +33,7 @@ public class SymbolsServiceTests : IDisposable private readonly PsesInternalHost psesHost; private readonly WorkspaceService workspace; private readonly SymbolsService symbolsService; + private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); public SymbolsServiceTests() { @@ -223,6 +225,15 @@ public async Task FindsReferencesOnVariable() Assert.Equal(13, referencesResult[referencesResult.Count - 1].ScriptRegion.StartColumnNumber); } + [Fact] + public void FindsOccurrencesOnVariable() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails); + Assert.Equal(3, occurrencesResult.Count); + Assert.Equal(10, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(13, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartColumnNumber); + } + [Fact] public void FindsOccurrencesOnFunction() { @@ -251,6 +262,222 @@ public async Task FindsReferencesOnCommandWithAlias() Assert.Equal("Get-ChildItem", referencesResult[referencesResult.Count - 1].SymbolName); } + [Fact] + public async Task FindsClassDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); + Assert.Equal(8, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(7, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnClass() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true); + Assert.Equal(2, referencesResult.Count); + Assert.Equal(8, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(7, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnClass() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ClassSourceDetails); + Assert.Equal(2, occurrencesResult.Count); + Assert.Equal("[SuperClass]", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(34, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsEnumDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true); + Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnEnum() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true); + Assert.Equal(4, referencesResult.Count); + Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnEnum() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumSourceDetails); + Assert.Equal(4, occurrencesResult.Count); + Assert.Equal("[MyEnum]", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(46, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsTypeExpressionDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true); + Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnTypeExpression() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails).ConfigureAwait(true); + Assert.Equal(2, referencesResult.Count); + Assert.Equal(8, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(7, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnTypeExpression() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeExpressionSourceDetails); + Assert.Equal(2, occurrencesResult.Count); + Assert.Equal("SuperClass", occurrencesResult[0].SymbolName); + Assert.Equal(8, occurrencesResult[0].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsTypeConstraintDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true); + Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnTypeConstraint() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true); + Assert.Equal(4, referencesResult.Count); + Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnTypeConstraint() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeConstraintSourceDetails); + Assert.Equal(2, occurrencesResult.Count); + Assert.Equal("BaseClass", occurrencesResult[0].SymbolName); + Assert.Equal(4, occurrencesResult[0].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsConstructorDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ConstructorSourceDetails).ConfigureAwait(true); + Assert.Equal(9, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(5, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass.SuperClass([string]$name)", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnConstructor() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(9, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(5, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnConstructor() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ConstructorSourceDetails); + Assert.Single(occurrencesResult); + Assert.Equal("SuperClass.SuperClass()", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(13, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsMethodDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.MethodSourceDetails).ConfigureAwait(true); + Assert.Equal(19, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(13, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnMethod() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(13, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnMethod() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails); + Assert.Single(occurrencesResult); + Assert.Equal("SuperClass.MyClassMethod()", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(28, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsPropertyDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); + Assert.Equal(15, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(13, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass.SomePropWithDefault", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnProperty() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(17, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnProperty() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails); + Assert.Equal(1, occurrencesResult.Count); + Assert.Equal("SuperClass.SomePropWithDefault", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(15, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsEnumMemberDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Equal(41, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(5, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum.Second", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnEnumMember() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(41, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(5, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnEnumMember() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails); + Assert.Single(occurrencesResult); + Assert.Equal("MyEnum.First", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(40, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + [Fact] public async Task FindsReferencesOnFileWithReferencesFileB() { @@ -277,6 +504,50 @@ public async Task FindsDetailsForBuiltInCommand() Assert.NotEqual("", symbolDetails.Documentation); } + [Fact] + public async Task FindsDetailsWithSignatureForEnumMember() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails), + FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("MyEnum.First", symbolDetails.DisplayString); + } + + [Fact] + public async Task FindsDetailsWithSignatureForProperty() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.PropertySourceDetails), + FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("string SuperClass.SomePropWithDefault", symbolDetails.DisplayString); + } + + [Fact] + public async Task FindsDetailsWithSignatureForConstructor() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.ConstructorSourceDetails), + FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("SuperClass.SuperClass([string]$name)", symbolDetails.DisplayString); + } + + [Fact] + public async Task FindsDetailsWithSignatureForMethod() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.MethodSourceDetails), + FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("string SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbolDetails.DisplayString); + } + [Fact] public void FindsSymbolsInFile() { @@ -287,11 +558,17 @@ public void FindsSymbolsInFile() Assert.Equal(4, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); Assert.Equal(7, firstFunctionSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(10, firstFunctionSymbol.ScriptRegion.StartColumnNumber); SymbolReference lastVariableSymbol = symbolsResult.Last(r => r.SymbolType == SymbolType.Variable); Assert.Equal("$Script:ScriptVar2", lastVariableSymbol.SymbolName); @@ -301,14 +578,102 @@ public void FindsSymbolsInFile() SymbolReference firstWorkflowSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Workflow); Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName); Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); - - // TODO: Bring this back when we can use AstVisitor2 again (#276) - //Assert.Equal(1, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).Count()); - //SymbolReference firstConfigurationSymbol = symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).First(); - //Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); - //Assert.Equal(25, firstConfigurationSymbol.ScriptRegion.StartLineNumber); - //Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(10, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); + Assert.Equal("AClass", firstClassSymbol.SymbolName); + Assert.Equal(25, firstClassSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(7, firstClassSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); + Assert.Equal("AProperty", firstPropertySymbol.SymbolName); + Assert.Equal(26, firstPropertySymbol.ScriptRegion.StartLineNumber); + Assert.Equal(13, firstPropertySymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); + Assert.Equal("AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); + Assert.Equal(28, firstConstructorSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); + Assert.Equal("AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); + Assert.Equal(32, firstMethodSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(11, firstMethodSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); + Assert.Equal("AEnum", firstEnumSymbol.SymbolName); + Assert.Equal(37, firstEnumSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(6, firstEnumSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumMemberSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.EnumMember); + Assert.Equal("AValue", firstEnumMemberSymbol.SymbolName); + Assert.Equal(38, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsSymbolsWithNewLineInFile() + { + List symbolsResult = + FindSymbolsInFile( + FindSymbolsInNewLineSymbolFile.SourceDetails); + + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); + + SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); + Assert.Equal("returnTrue", firstFunctionSymbol.SymbolName); + Assert.Equal(2, firstFunctionSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); + Assert.Equal("NewLineClass", firstClassSymbol.SymbolName); + Assert.Equal(7, firstClassSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstClassSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); + Assert.Equal("NewLineClass()", firstConstructorSymbol.SymbolName); + Assert.Equal(8, firstConstructorSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); + Assert.Equal("SomePropWithDefault", firstPropertySymbol.SymbolName); + Assert.Equal(15, firstPropertySymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstPropertySymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); + Assert.Equal("MyClassMethod([MyNewLineEnum]$param1)", firstMethodSymbol.SymbolName); + Assert.Equal(20, firstMethodSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstMethodSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); + Assert.Equal("MyNewLineEnum", firstEnumSymbol.SymbolName); + Assert.Equal(26, firstEnumSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstEnumSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumMemberSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.EnumMember); + Assert.Equal("First", firstEnumMemberSymbol.SymbolName); + Assert.Equal(27, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); + } + + [SkippableFact] + public void FindsSymbolsInDSCFile() + { + Skip.If(!s_isWindows, "DSC only works properly on Windows."); + + List symbolsResult = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); + + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Configuration)); + SymbolReference firstConfigurationSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Configuration); + Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); + Assert.Equal(2, firstConfigurationSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(15, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); } [Fact]