/// <summary> /// AnalyzeScript: Run Test Module Manifest to check that none of the required fields are missing. From the ILintScriptRule interface. /// </summary> /// <param name="ast">The script's ast</param> /// <param name="fileName">The script's file name</param> /// <returns>A List of diagnostic results of this rule</returns> public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); if (String.Equals(System.IO.Path.GetExtension(fileName), ".psd1", StringComparison.OrdinalIgnoreCase)) { IEnumerable<ErrorRecord> errorRecords; var psModuleInfo = Helper.Instance.GetModuleManifest(fileName, out errorRecords); if (errorRecords != null) { foreach (var errorRecord in errorRecords) { if (Helper.IsMissingManifestMemberException(errorRecord)) { System.Diagnostics.Debug.Assert( errorRecord.Exception != null && !String.IsNullOrWhiteSpace(errorRecord.Exception.Message), Strings.NullErrorMessage); var hashTableAst = ast.Find(x => x is HashtableAst, false); if (hashTableAst == null) { yield break; } yield return new DiagnosticRecord( errorRecord.Exception.Message, hashTableAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, suggestedCorrections:GetCorrectionExtent(hashTableAst as HashtableAst)); } } } } }
/// <summary> /// AnalyzeScript: Analyzes the AST to check if AliasToExport, CmdletsToExport, FunctionsToExport /// and VariablesToExport fields do not use wildcards and $null in their entries. /// </summary> /// <param name="ast">The script's ast</param> /// <param name="fileName">The script's file name</param> /// <returns>A List of diagnostic results of this rule</returns> public IEnumerable <DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) { if (ast == null) { throw new ArgumentNullException(Strings.NullAstErrorMessage); } if (fileName == null || !fileName.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) { yield break; } if (!IsValidManifest(ast, fileName)) { yield break; } String[] manifestFields = { "FunctionsToExport", "CmdletsToExport", "VariablesToExport", "AliasesToExport" }; var hashtableAst = ast.Find(x => x is HashtableAst, false) as HashtableAst; if (hashtableAst == null) { yield break; } foreach (String field in manifestFields) { IScriptExtent extent; if (!HasAcceptableExportField(field, hashtableAst, ast.Extent.Text, out extent) && extent != null) { yield return(new DiagnosticRecord(GetError(field), extent, GetName(), DiagnosticSeverity.Warning, fileName)); } } }
/// <summary> /// AnalyzeScript: Analyzes the AST to check if AliasToExport, CmdletsToExport, FunctionsToExport /// and VariablesToExport fields do not use wildcards and $null in their entries. /// </summary> /// <param name="ast">The script's ast</param> /// <param name="fileName">The script's file name</param> /// <returns>A List of diagnostic results of this rule</returns> public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) { if (ast == null) { throw new ArgumentNullException(Strings.NullAstErrorMessage); } if (fileName == null || !fileName.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) { yield break; } if (!IsValidManifest(ast, fileName)) { yield break; } String[] manifestFields = {"FunctionsToExport", "CmdletsToExport", "VariablesToExport", "AliasesToExport"}; var hashtableAst = ast.Find(x => x is HashtableAst, false) as HashtableAst; if (hashtableAst == null) { yield break; } foreach(String field in manifestFields) { IScriptExtent extent; if (!HasAcceptableExportField(field, hashtableAst, ast.Extent.Text, out extent) && extent != null) { yield return new DiagnosticRecord(GetError(field), extent, GetName(), DiagnosticSeverity.Warning, fileName); } } }
/// <summary> /// Finds all possible function definitions for the token at the caret position /// </summary> public static IEnumerable <FunctionDefinitionAst> FindFunctionDefinitions(Ast script, ITextSnapshot currentSnapshot, int caretPosition) { if (script != null) { var reference = script.Find(node => node is CommandAst && caretPosition >= node.Extent.StartOffset && caretPosition <= node.Extent.EndOffset, true) as CommandAst; FunctionDefinitionAst definition = null; if (reference != null) { return(FindDefinition(reference)); } else { definition = script.Find(node => { if (node is FunctionDefinitionAst) { var functionNameSpan = GetFunctionNameSpan(node as FunctionDefinitionAst); return(functionNameSpan.HasValue && caretPosition >= functionNameSpan.Value.Start && caretPosition <= functionNameSpan.Value.End); } return(false); }, true) as FunctionDefinitionAst; if (definition != null) { return(new List <FunctionDefinitionAst>() { definition }); } } } return(null); }
/// <summary> /// Gets the CommandAst for the whole command line. /// </summary> /// <param name="commandLine">The command line to get the CommandAst.</param> /// <returns>The CommandAst.</returns> /// <remarks>This parses the command line and returns the first one it encounters. It doesn't work well in a complex command line, for example: <c>Get-AzContext | Set-AzContext</c> will return <c>Get-AzContext</c>.</remarks> public static CommandAst GetCommandAst(string commandLine) { if (string.IsNullOrWhiteSpace(commandLine)) { return(null); } Ast ast = Parser.ParseInput(commandLine, out _, out _); var commandAst = ast.Find((ast) => ast is CommandAst, searchNestedScriptBlocks: false) as CommandAst; return(commandAst); }
/// <summary> /// AnalyzeScript: Analyzes the AST to check if AliasToExport, CmdletsToExport, FunctionsToExport /// and VariablesToExport fields do not use wildcards and $null in their entries. /// </summary> /// <param name="ast">The script's ast</param> /// <param name="fileName">The script's file name</param> /// <returns>A List of diagnostic results of this rule</returns> public IEnumerable <DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) { if (ast == null) { throw new ArgumentNullException(Strings.NullAstErrorMessage); } if (fileName == null || !fileName.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) { yield break; } // check if valid module manifest IEnumerable <ErrorRecord> errorRecord = null; PSModuleInfo psModuleInfo = Helper.Instance.GetModuleManifest(fileName, out errorRecord); if ((errorRecord != null && errorRecord.Count() > 0) || psModuleInfo == null) { yield break; } var hashtableAst = ast.Find(x => x is HashtableAst, false) as HashtableAst; if (hashtableAst == null) { yield break; } string[] manifestFields = { functionsToExport, cmdletsToExport, aliasesToExport }; foreach (string field in manifestFields) { IScriptExtent extent; if (!HasAcceptableExportField(field, hashtableAst, ast.Extent.Text, out extent) && extent != null) { yield return(new DiagnosticRecord( GetError(field), extent, GetName(), DiagnosticSeverity.Warning, fileName, suggestedCorrections: GetCorrectionExtent(field, extent, psModuleInfo))); } else { } } }
/// <summary> /// Predictor must be initialized with a list of string suggestions. /// </summary> /// <param name="modelPredictions">List of suggestions from the model, sorted by frequency (most to least)</param> /// <param name="parameterValuePredictor">Provide the prediction to the parameter values.</param> public Predictor(IList <string> modelPredictions, ParameterValuePredictor parameterValuePredictor) { this._parameterValuePredictor = parameterValuePredictor; this._predictions = new List <Prediction>(); foreach (var predictionTextRaw in modelPredictions ?? Enumerable.Empty <string>()) { var predictionText = EscapePredictionText(predictionTextRaw); Ast ast = Parser.ParseInput(predictionText, out Token[] tokens, out _); var commandAst = (ast.Find((ast) => ast is CommandAst, searchNestedScriptBlocks: false) as CommandAst); if (commandAst?.CommandElements[0] is StringConstantExpressionAst commandName) { var parameterSet = new ParameterSet(commandAst); this._predictions.Add(new Prediction(commandName.Value, parameterSet)); } } }
/// <summary> /// Create a new instance of <see cref="CommandLine"/> from <see cref="PredictiveCommand" />. /// </summary> /// <param name="predictiveCommand">The command information.</param> /// <param name="azContext">The current PowerShell conext.</param> public CommandLine(PredictiveCommand predictiveCommand, IAzContext azContext = null) { Validation.CheckArgument(predictiveCommand, $"{nameof(predictiveCommand)} cannot be null."); var predictionText = CommandLineUtilities.EscapePredictionText(predictiveCommand.Command); Ast ast = Parser.ParseInput(predictionText, out Token[] tokens, out _); var commandAst = ast.Find((ast) => ast is CommandAst, searchNestedScriptBlocks: false) as CommandAst; var commandName = commandAst?.GetCommandName(); Validation.CheckInvariant <CommandLineException>(!string.IsNullOrWhiteSpace(commandName), $"Cannot get the command name from the model {predictiveCommand.Command}"); var parameterSet = new ParameterSet(commandAst, azContext); Name = commandName; Description = predictiveCommand.Description; ParameterSet = parameterSet; SourceText = predictiveCommand.Command; }
public static IEnumerable <Token> GetTokens(Ast outerAst, Ast innerAst, Token[] outerTokens) { ThrowIfNull(outerAst, nameof(outerAst)); ThrowIfNull(innerAst, nameof(innerAst)); ThrowIfNull(outerTokens, nameof(outerTokens)); // check if inner ast belongs in outerAst var foundAst = outerAst.Find(x => x.Equals(innerAst), true); if (foundAst == null) { // todo localize throw new ArgumentException(String.Format("innerAst cannot be found within outerAst")); } var tokenOps = new TokenOperations(outerTokens, outerAst); return(tokenOps.GetTokens(innerAst)); }
/// <summary> /// Creates a new instance of <see cref="CommandLinePredictor"/>. /// </summary> /// <param name="modelPredictions">List of suggestions from the model, sorted by frequency (most to least).</param> /// <param name="parameterValuePredictor">Provide the prediction to the parameter values.</param> public CommandLinePredictor(IList <PredictiveCommand> modelPredictions, ParameterValuePredictor parameterValuePredictor) { Validation.CheckArgument(modelPredictions, $"{nameof(modelPredictions)} cannot be null."); _parameterValuePredictor = parameterValuePredictor; var commnadLines = new List <CommandLine>(); foreach (var predictiveCommand in modelPredictions ?? Enumerable.Empty <PredictiveCommand>()) { var predictionText = CommandLineUtilities.EscapePredictionText(predictiveCommand.Command); Ast ast = Parser.ParseInput(predictionText, out Token[] tokens, out _); var commandAst = (ast.Find((ast) => ast is CommandAst, searchNestedScriptBlocks: false) as CommandAst); if (commandAst?.CommandElements[0] is StringConstantExpressionAst commandName) { var parameterSet = new ParameterSet(commandAst); this._commandLinePredictions.Add(new CommandLine(commandName.Value, predictiveCommand.Description, parameterSet)); } } }
/// <summary> /// AnalyzeScript: Run Test Module Manifest to check that none of the required fields are missing. From the ILintScriptRule interface. /// </summary> /// <param name="ast">The script's ast</param> /// <param name="fileName">The script's file name</param> /// <returns>A List of diagnostic results of this rule</returns> public IEnumerable <DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) { if (ast == null) { throw new ArgumentNullException(Strings.NullAstErrorMessage); } if (fileName == null) { yield break; } if (Helper.IsModuleManifest(fileName)) { IEnumerable <ErrorRecord> errorRecords; var psModuleInfo = Helper.Instance.GetModuleManifest(fileName, out errorRecords); if (errorRecords != null) { foreach (var errorRecord in errorRecords) { if (Helper.IsMissingManifestMemberException(errorRecord)) { System.Diagnostics.Debug.Assert( errorRecord.Exception != null && !String.IsNullOrWhiteSpace(errorRecord.Exception.Message), Strings.NullErrorMessage); var hashTableAst = ast.Find(x => x is HashtableAst, false); if (hashTableAst == null) { yield break; } yield return(new DiagnosticRecord( errorRecord.Exception.Message, hashTableAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, suggestedCorrections: GetCorrectionExtent(hashTableAst as HashtableAst))); } } } } }
public void Execute(object sender, EventArgs args) { var commandAst = _ast.Find(m => m.Extent.StartOffset == _offset, true) as CommandAst; if (commandAst == null) { MessageBox.Show("Whoops! Something went wrong finding the definition of that function!", "Command Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } var functionDefinitionAst = FindDefinition(commandAst, commandAst.Parent); if (functionDefinitionAst == null) { MessageBox.Show("Unable to locate the definition to that function.", "Command Warning", MessageBoxButton.OK, MessageBoxImage.Error); return; } var dte2 = (DTE2)Package.GetGlobalService(typeof(SDTE)); if (dte2 != null) { var buffer = _textBuffers.FirstOrDefault( m => m.GetFilePath() != null && m.GetFilePath().Equals(_fileName, StringComparison.OrdinalIgnoreCase)); if (buffer != null) { var ts = dte2.ActiveDocument.Selection as ITextSelection; if (ts != null) { ts.Select(new SnapshotSpan(buffer.CurrentSnapshot, _offset, 0), false); } } } }
/// <summary> /// Try to find a Param block on the top level of an AST. /// </summary> /// <param name="ast">The targeting AST.</param> /// <param name="paramBlockAst">Wanted Param block.</param> /// <returns>True if there is one. Otherwise, false.</returns> public static bool HasParamBlock(Ast ast, out ParamBlockAst paramBlockAst) { paramBlockAst = (ParamBlockAst)ast.Find(p => p is ParamBlockAst, false); return(paramBlockAst != null); }
// Quick check for script blocks that may have suspicious content. If this // is true, we log them to the event log despite event log settings. // // Performance notes: // // For the current number of search terms, the this approach is about as high // performance as we can get. It adds about 1ms to the invocation of a script // block (we don't do this at parse time). // The manual tokenization is much faster than either Regex.Split // or a series of String.Split calls. Lookups in the HashSet are much faster // than a ton of calls to String.IndexOf (which .NET implements in native code). // // If we were to expand this set of keywords much farther, it would make sense // to look into implementing the Aho-Corasick algorithm (the one used by antimalware // engines), but Aho-Corasick is slower than the current approach for relatively // small match sets. internal static string CheckSuspiciousContent(Ast scriptBlockAst) { // Split the script block text into an array of elements that have // a-Z A-Z dash. string scriptBlockText = scriptBlockAst.Extent.Text; IEnumerable<string> elements = TokenizeWordElements(scriptBlockText); // First check for plain-text signatures ParallelOptions parallelOptions = new ParallelOptions(); string foundSignature = null; Parallel.ForEach(elements, parallelOptions, (element, loopState) => { if (foundSignature == null) { if (s_signatures.Contains(element)) { foundSignature = element; loopState.Break(); } } }); if (!String.IsNullOrEmpty(foundSignature)) { return foundSignature; } if (scriptBlockAst.HasSuspiciousContent) { Ast foundAst = scriptBlockAst.Find(ast => { // Try to find the lowest AST that was not considered suspicious, but its parent // was. return (!ast.HasSuspiciousContent) && (ast.Parent.HasSuspiciousContent); }, true); if (foundAst != null) { return foundAst.Parent.Extent.Text; } else { return scriptBlockAst.Extent.Text; } } return null; }