Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
using System.Collections.Generic;

namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
{
/// <summary>
/// Class to encapsulate the request type.
/// </summary>
class ScriptFileMarkersRequest
{
public static readonly
RequestType<ScriptFileMarkerRequestParams, ScriptFileMarkerRequestResultParams> Type =
RequestType<ScriptFileMarkerRequestParams, ScriptFileMarkerRequestResultParams>.Create("powerShell/getScriptFileMarkers");
}

/// <summary>
/// Class to encapsulate the request parameters.
/// </summary>
class ScriptFileMarkerRequestParams
{
/// <summary>
/// Path of the file for which the markers are requested.
/// </summary>
public string filePath;

/// <summary>
/// Settings to be provided to ScriptAnalyzer to get the markers.
///
/// We have this unusual structure because JSON deserializer
/// does not deserialize nested hashtables. i.e. it won't
/// deserialize a hashtable within a hashtable. But in this case,
/// i.e. a hashtable within a dictionary, it will deserialize
/// the hashtable.
/// </summary>
public Dictionary<string, Hashtable> settings;
}

/// <summary>
/// Class to encapsulate the result of marker request.
/// </summary>
class ScriptFileMarkerRequestResultParams
{
/// <summary>
/// An array of markers obtained by analyzing the given file.
/// </summary>
public ScriptFileMarker[] markers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<Compile Include="LanguageServer\InstallModuleRequest.cs" />
<Compile Include="LanguageServer\PowerShellVersionRequest.cs" />
<Compile Include="LanguageServer\RunspaceChanged.cs" />
<Compile Include="LanguageServer\ScriptFileMarkersRequest.cs" />
<Compile Include="LanguageServer\SetPSSARulesRequest.cs" />
<Compile Include="LanguageServer\ProjectTemplate.cs" />
<Compile Include="MessageProtocol\Channel\NamedPipeClientChannel.cs" />
Expand Down
15 changes: 15 additions & 0 deletions src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.PowerShell.EditorServices.Utility;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -123,6 +124,8 @@ protected override void Initialize()
this.SetRequestHandler(GetPSSARulesRequest.Type, this.HandleGetPSSARulesRequest);
this.SetRequestHandler(SetPSSARulesRequest.Type, this.HandleSetPSSARulesRequest);

this.SetRequestHandler(ScriptFileMarkersRequest.Type, this.HandleScriptFileMarkersRequest);

this.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequest);

// Initialize the extension service
Expand Down Expand Up @@ -232,6 +235,18 @@ await RunScriptDiagnostics(
await sendresult;
}

private async Task HandleScriptFileMarkersRequest(
ScriptFileMarkerRequestParams requestParams,
RequestContext<ScriptFileMarkerRequestResultParams> requestContext)
{
var markers = editorSession.AnalysisService.GetSemanticMarkers(
editorSession.Workspace.GetFile(requestParams.filePath),
editorSession.AnalysisService.GetPSSASettingsHashtable(requestParams.settings));
await requestContext.SendResult(new ScriptFileMarkerRequestResultParams {
markers = markers
});
}

private async Task HandleGetPSSARulesRequest(
object param,
RequestContext<object> requestContext)
Expand Down
117 changes: 87 additions & 30 deletions src/PowerShellEditorServices/Analysis/AnalysisService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Management.Automation;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Microsoft.PowerShell.EditorServices
{
Expand Down Expand Up @@ -128,38 +129,25 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null)
#region Public Methods

/// <summary>
/// Performs semantic analysis on the given ScriptFile and returns
/// Perform semantic analysis on the given ScriptFile and returns
/// an array of ScriptFileMarkers.
/// </summary>
/// <param name="file">The ScriptFile which will be analyzed for semantic markers.</param>
/// <returns>An array of ScriptFileMarkers containing semantic analysis results.</returns>
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file)
{
if (this.scriptAnalyzerModuleInfo != null && file.IsAnalysisEnabled)
{
// TODO: This is a temporary fix until we can change how
// ScriptAnalyzer invokes their async tasks.
// TODO: Make this async
Task<ScriptFileMarker[]> analysisTask =
Task.Factory.StartNew<ScriptFileMarker[]>(
() =>
{
return
GetDiagnosticRecords(file)
.Select(ScriptFileMarker.FromDiagnosticRecord)
.ToArray();
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
analysisTask.Wait();
return analysisTask.Result;
}
else
{
// Return an empty marker list
return new ScriptFileMarker[0];
}
return GetSemanticMarkers(file, activeRules, settingsPath);
}

/// <summary>
/// Perform semantic analysis on the given ScriptFile with the given settings.
/// </summary>
/// <param name="file">The ScriptFile to be analyzed.</param>
/// <param name="settings">ScriptAnalyzer settings</param>
/// <returns></returns>
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file, Hashtable settings)
{
return GetSemanticMarkers<Hashtable>(file, null, settings);
}

/// <summary>
Expand Down Expand Up @@ -187,6 +175,27 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
return ruleNames;
}

/// <summary>
/// Construct a PSScriptAnalyzer settings hashtable
/// </summary>
/// <param name="ruleSettingsMap">A settings hashtable</param>
/// <returns></returns>
public Hashtable GetPSSASettingsHashtable(IDictionary<string, Hashtable> ruleSettingsMap)
{
var hashtable = new Hashtable();
var ruleSettingsHashtable = new Hashtable();

hashtable["IncludeRules"] = ruleSettingsMap.Keys.ToArray<object>();
hashtable["Rules"] = ruleSettingsHashtable;

foreach (var kvp in ruleSettingsMap)
{
ruleSettingsHashtable.Add(kvp.Key, kvp.Value);
}

return hashtable;
}

/// <summary>
/// Disposes the runspace being used by the analysis service.
/// </summary>
Expand All @@ -203,6 +212,42 @@ public void Dispose()
#endregion // public methods

#region Private Methods

private ScriptFileMarker[] GetSemanticMarkers<TSettings>(
ScriptFile file,
string[] rules,
TSettings settings) where TSettings : class
{
if (this.scriptAnalyzerModuleInfo != null
&& file.IsAnalysisEnabled
&& (typeof(TSettings) == typeof(string) || typeof(TSettings) == typeof(Hashtable))
&& (rules != null || settings != null))
{
// TODO: This is a temporary fix until we can change how
// ScriptAnalyzer invokes their async tasks.
// TODO: Make this async
Task<ScriptFileMarker[]> analysisTask =
Task.Factory.StartNew<ScriptFileMarker[]>(
() =>
{
return
GetDiagnosticRecords(file, rules, settings)
.Select(ScriptFileMarker.FromDiagnosticRecord)
.ToArray();
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
analysisTask.Wait();
return analysisTask.Result;
}
else
{
// Return an empty marker list
return new ScriptFileMarker[0];
}
}

private void FindPSScriptAnalyzer()
{
lock (runspaceLock)
Expand Down Expand Up @@ -291,10 +336,22 @@ private void InitializePSScriptAnalyzer()
}

private IEnumerable<PSObject> GetDiagnosticRecords(ScriptFile file)
{
return GetDiagnosticRecords(file, this.activeRules, this.settingsPath);
}

// TSettings can either be of type Hashtable or string
// as scriptanalyzer settings parameter takes either a hashtable or string
private IEnumerable<PSObject> GetDiagnosticRecords<TSettings>(
ScriptFile file,
string[] rules,
TSettings settings) where TSettings: class
{
IEnumerable<PSObject> diagnosticRecords = Enumerable.Empty<PSObject>();

if (this.scriptAnalyzerModuleInfo != null)
if (this.scriptAnalyzerModuleInfo != null
&& (typeof(TSettings) == typeof(string)
|| typeof(TSettings) == typeof(Hashtable)))
{
lock (runspaceLock)
{
Expand All @@ -310,13 +367,13 @@ private IEnumerable<PSObject> GetDiagnosticRecords(ScriptFile file)
.AddParameter("ScriptDefinition", file.Contents);

// Use a settings file if one is provided, otherwise use the default rule list.
if (!string.IsNullOrWhiteSpace(this.SettingsPath))
if (settings != null)
{
powerShell.AddParameter("Settings", this.SettingsPath);
powerShell.AddParameter("Settings", settings);
}
else
{
powerShell.AddParameter("IncludeRule", activeRules);
powerShell.AddParameter("IncludeRule", rules);
}

diagnosticRecords = powerShell.Invoke();
Expand Down
18 changes: 10 additions & 8 deletions src/PowerShellEditorServices/Language/LanguageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

using Microsoft.PowerShell.EditorServices.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -132,7 +134,7 @@ await this.powerShellContext.GetRunspaceHandle(
}

/// <summary>
/// Finds command completion details for the script given a file location
/// Finds command completion details for the script given a file location
/// </summary>
/// <param name="file">The details and contents of a open script file</param>
/// <param name="entryName">The name of the suggestion that needs details</param>
Expand All @@ -144,7 +146,7 @@ public CompletionDetails GetCompletionDetailsInFile(
// Makes sure the most recent completions request was the same line and column as this request
if (file.Id.Equals(mostRecentRequestFile))
{
CompletionDetails completionResult =
CompletionDetails completionResult =
mostRecentCompletions.Completions.FirstOrDefault(
result => result.CompletionText.Equals(entryName));

Expand All @@ -163,7 +165,7 @@ public CompletionDetails GetCompletionDetailsInFile(
/// <param name="lineNumber">The line number of the cursor for the given script</param>
/// <param name="columnNumber">The coulumn number of the cursor for the given script</param>
/// <returns>A SymbolReference of the symbol found at the given location
/// or null if there is no symbol at that location
/// or null if there is no symbol at that location
/// </returns>
public SymbolReference FindSymbolAtLocation(
ScriptFile scriptFile,
Expand Down Expand Up @@ -255,7 +257,7 @@ public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile)
public async Task<FindReferencesResult> FindReferencesOfSymbol(
SymbolReference foundSymbol,
ScriptFile[] referencedFiles)
{
{
if (foundSymbol != null)
{
int symbolOffset = referencedFiles[0].GetOffsetAtPosition(
Expand Down Expand Up @@ -336,7 +338,7 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
}
}

// if definition is not found in referenced files
// if definition is not found in referenced files
// look for it in the builtin commands
if (foundDefinition == null)
{
Expand All @@ -345,9 +347,9 @@ await CommandHelpers.GetCommandInfo(
foundSymbol.SymbolName,
this.powerShellContext);

foundDefinition =
foundDefinition =
FindDeclarationForBuiltinCommand(
cmdInfo,
cmdInfo,
foundSymbol,
workspace);
}
Expand Down Expand Up @@ -533,7 +535,7 @@ private ScriptFile[] GetBuiltinCommandScriptFiles(
}

private SymbolReference FindDeclarationForBuiltinCommand(
CommandInfo commandInfo,
CommandInfo commandInfo,
SymbolReference foundSymbol,
Workspace workspace)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<Compile Include="..\PowerShellEditorServices.Protocol\LanguageServer\GetPSSARulesRequest.cs">
<Link>GetPSSARulesRequest.cs</Link>
</Compile>
<Compile Include="..\PowerShellEditorServices.Protocol\LanguageServer\ScriptFileMarkersRequest.cs">
<Link>ScriptFileMarkersRequest.cs</Link>
</Compile>
<Compile Include="..\PowerShellEditorServices.Protocol\LanguageServer\SetPSSARulesRequest.cs">
<Link>SetPSSARulesRequest.cs</Link>
</Compile>
Expand Down