/// <summary> /// Test if the given Ast is a regular CommandAst with arguments /// </summary> /// <param name="ast">the PowerShell Ast to test</param> /// <returns>true if the Ast represents a PowerShell command with arguments, false otherwise</returns> private static bool IsNamedCommandWithArguments(Ast ast) { CommandAst commandAst = ast as CommandAst; return(commandAst != null && commandAst.InvocationOperator != TokenKind.Dot && PesterSymbolReference.GetCommandType(commandAst.GetCommandName()).HasValue&& commandAst.CommandElements.Count >= 2); }
/// <summary> /// Convert a CommandAst known to represent a Pester command and a reference to the scriptfile /// it is in into symbol representing a Pester call for code lens /// </summary> /// <param name="scriptFile">the scriptfile the Pester call occurs in</param> /// <param name="pesterCommandAst">the CommandAst representing the Pester call</param> /// <returns>a symbol representing the Pester call containing metadata for CodeLens to use</returns> private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFile scriptFile, CommandAst pesterCommandAst) { string testLine = scriptFile.GetLine(pesterCommandAst.Extent.StartLineNumber); PesterCommandType?commandName = PesterSymbolReference.GetCommandType(pesterCommandAst.GetCommandName()); if (commandName == null) { return(null); } // Search for a name for the test // If the test has more than one argument for names, we set it to null string testName = null; bool alreadySawName = false; for (int i = 1; i < pesterCommandAst.CommandElements.Count; i++) { CommandElementAst currentCommandElement = pesterCommandAst.CommandElements[i]; // Check for an explicit "-Name" parameter if (currentCommandElement is CommandParameterAst parameterAst) { // Found -Name parameter, move to next element which is the argument for -TestName i++; if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) { alreadySawName = true; } continue; } // Otherwise, if an argument is given with no parameter, we assume it's the name // If we've already seen a name, we set the name to null if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) { alreadySawName = true; } } return(new PesterSymbolReference( scriptFile, commandName.Value, testLine, testName, pesterCommandAst.Extent )); }
/// <summary> /// Convert a CommandAst known to represent a Pester command and a reference to the scriptfile /// it is in into symbol representing a Pester call for code lens /// </summary> /// <param name="scriptFile">the scriptfile the Pester call occurs in</param> /// <param name="pesterCommandAst">the CommandAst representing the Pester call</param> /// <returns>a symbol representing the Pester call containing metadata for CodeLens to use</returns> private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFile scriptFile, CommandAst pesterCommandAst) { string testLine = scriptFile.GetLine(pesterCommandAst.Extent.StartLineNumber); PesterCommandType?commandName = PesterSymbolReference.GetCommandType(pesterCommandAst.GetCommandName()); if (commandName == null) { return(null); } // Search for a name for the test // If the test has more than one argument for names, we set it to null string testName = null; bool alreadySawName = false; for (int i = 1; i < pesterCommandAst.CommandElements.Count; i++) { CommandElementAst currentCommandElement = pesterCommandAst.CommandElements[i]; // Check for an explicit "-Name" parameter if (currentCommandElement is CommandParameterAst parameterAst) { i++; if (parameterAst.ParameterName == "Name" && i < pesterCommandAst.CommandElements.Count) { testName = alreadySawName ? null : (pesterCommandAst.CommandElements[i] as StringConstantExpressionAst)?.Value; alreadySawName = true; } continue; } // Otherwise, if an argument is given with no parameter, we assume it's the name // If we've already seen a name, we set the name to null if (pesterCommandAst.CommandElements[i] is StringConstantExpressionAst testNameStrAst) { testName = alreadySawName ? null : testNameStrAst.Value; alreadySawName = true; } } return(new PesterSymbolReference( scriptFile, commandName.Value, testLine, testName, pesterCommandAst.Extent )); }
IEnumerable <SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { if (!scriptFile.FilePath.EndsWith( "tests.ps1", StringComparison.OrdinalIgnoreCase)) { return(Enumerable.Empty <SymbolReference>()); } var commandAsts = scriptFile.ScriptAst.FindAll(ast => { CommandAst commandAst = ast as CommandAst; return (commandAst != null && commandAst.InvocationOperator != TokenKind.Dot && PesterSymbolReference.GetCommandType(commandAst.GetCommandName()).HasValue&& commandAst.CommandElements.Count >= 2); }, true); return(commandAsts.Select( ast => { // By this point we know the Ast is a CommandAst with 2 or more CommandElements int testNameParamIndex = 1; CommandAst testAst = (CommandAst)ast; // The -Name parameter for (int i = 1; i < testAst.CommandElements.Count; i++) { CommandParameterAst paramAst = testAst.CommandElements[i] as CommandParameterAst; if (paramAst != null && paramAst.ParameterName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { testNameParamIndex = i + 1; break; } } if (testNameParamIndex > testAst.CommandElements.Count - 1) { return null; } StringConstantExpressionAst stringAst = testAst.CommandElements[testNameParamIndex] as StringConstantExpressionAst; if (stringAst == null) { return null; } string testDefinitionLine = scriptFile.GetLine( ast.Extent.StartLineNumber); return new PesterSymbolReference( scriptFile, testAst.GetCommandName(), testDefinitionLine, stringAst.Value, ast.Extent); }).Where(s => s != null)); }