Skip to content

Commit f8f6f32

Browse files
authored
Merge pull request #332 from PowerShell/kapilmb/code-formatting
Add feature to format PowerShell source files
2 parents a4db350 + 56cf0df commit f8f6f32

File tree

6 files changed

+170
-38
lines changed

6 files changed

+170
-38
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Collections;
7+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
8+
using System.Collections.Generic;
9+
10+
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
11+
{
12+
/// <summary>
13+
/// Class to encapsulate the request type.
14+
/// </summary>
15+
class ScriptFileMarkersRequest
16+
{
17+
public static readonly
18+
RequestType<ScriptFileMarkerRequestParams, ScriptFileMarkerRequestResultParams> Type =
19+
RequestType<ScriptFileMarkerRequestParams, ScriptFileMarkerRequestResultParams>.Create("powerShell/getScriptFileMarkers");
20+
}
21+
22+
/// <summary>
23+
/// Class to encapsulate the request parameters.
24+
/// </summary>
25+
class ScriptFileMarkerRequestParams
26+
{
27+
/// <summary>
28+
/// Path of the file for which the markers are requested.
29+
/// </summary>
30+
public string filePath;
31+
32+
/// <summary>
33+
/// Settings to be provided to ScriptAnalyzer to get the markers.
34+
///
35+
/// We have this unusual structure because JSON deserializer
36+
/// does not deserialize nested hashtables. i.e. it won't
37+
/// deserialize a hashtable within a hashtable. But in this case,
38+
/// i.e. a hashtable within a dictionary, it will deserialize
39+
/// the hashtable.
40+
/// </summary>
41+
public Dictionary<string, Hashtable> settings;
42+
}
43+
44+
/// <summary>
45+
/// Class to encapsulate the result of marker request.
46+
/// </summary>
47+
class ScriptFileMarkerRequestResultParams
48+
{
49+
/// <summary>
50+
/// An array of markers obtained by analyzing the given file.
51+
/// </summary>
52+
public ScriptFileMarker[] markers;
53+
}
54+
}

src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<Compile Include="LanguageServer\InstallModuleRequest.cs" />
6565
<Compile Include="LanguageServer\PowerShellVersionRequest.cs" />
6666
<Compile Include="LanguageServer\RunspaceChanged.cs" />
67+
<Compile Include="LanguageServer\ScriptFileMarkersRequest.cs" />
6768
<Compile Include="LanguageServer\SetPSSARulesRequest.cs" />
6869
<Compile Include="LanguageServer\ProjectTemplate.cs" />
6970
<Compile Include="MessageProtocol\Channel\NamedPipeClientChannel.cs" />

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.PowerShell.EditorServices.Utility;
1313
using Newtonsoft.Json.Linq;
1414
using System;
15+
using System.Collections;
1516
using System.Collections.Generic;
1617
using System.IO;
1718
using System.Linq;
@@ -123,6 +124,8 @@ protected override void Initialize()
123124
this.SetRequestHandler(GetPSSARulesRequest.Type, this.HandleGetPSSARulesRequest);
124125
this.SetRequestHandler(SetPSSARulesRequest.Type, this.HandleSetPSSARulesRequest);
125126

127+
this.SetRequestHandler(ScriptFileMarkersRequest.Type, this.HandleScriptFileMarkersRequest);
128+
126129
this.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequest);
127130

128131
// Initialize the extension service
@@ -232,6 +235,18 @@ await RunScriptDiagnostics(
232235
await sendresult;
233236
}
234237

238+
private async Task HandleScriptFileMarkersRequest(
239+
ScriptFileMarkerRequestParams requestParams,
240+
RequestContext<ScriptFileMarkerRequestResultParams> requestContext)
241+
{
242+
var markers = editorSession.AnalysisService.GetSemanticMarkers(
243+
editorSession.Workspace.GetFile(requestParams.filePath),
244+
editorSession.AnalysisService.GetPSSASettingsHashtable(requestParams.settings));
245+
await requestContext.SendResult(new ScriptFileMarkerRequestResultParams {
246+
markers = markers
247+
});
248+
}
249+
235250
private async Task HandleGetPSSARulesRequest(
236251
object param,
237252
RequestContext<object> requestContext)

src/PowerShellEditorServices/Analysis/AnalysisService.cs

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Management.Automation;
1414
using System.Collections.Generic;
1515
using System.Text;
16+
using System.Collections;
1617

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

130131
/// <summary>
131-
/// Performs semantic analysis on the given ScriptFile and returns
132+
/// Perform semantic analysis on the given ScriptFile and returns
132133
/// an array of ScriptFileMarkers.
133134
/// </summary>
134135
/// <param name="file">The ScriptFile which will be analyzed for semantic markers.</param>
135136
/// <returns>An array of ScriptFileMarkers containing semantic analysis results.</returns>
136137
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file)
137138
{
138-
if (this.scriptAnalyzerModuleInfo != null && file.IsAnalysisEnabled)
139-
{
140-
// TODO: This is a temporary fix until we can change how
141-
// ScriptAnalyzer invokes their async tasks.
142-
// TODO: Make this async
143-
Task<ScriptFileMarker[]> analysisTask =
144-
Task.Factory.StartNew<ScriptFileMarker[]>(
145-
() =>
146-
{
147-
return
148-
GetDiagnosticRecords(file)
149-
.Select(ScriptFileMarker.FromDiagnosticRecord)
150-
.ToArray();
151-
},
152-
CancellationToken.None,
153-
TaskCreationOptions.None,
154-
TaskScheduler.Default);
155-
analysisTask.Wait();
156-
return analysisTask.Result;
157-
}
158-
else
159-
{
160-
// Return an empty marker list
161-
return new ScriptFileMarker[0];
162-
}
139+
return GetSemanticMarkers(file, activeRules, settingsPath);
140+
}
141+
142+
/// <summary>
143+
/// Perform semantic analysis on the given ScriptFile with the given settings.
144+
/// </summary>
145+
/// <param name="file">The ScriptFile to be analyzed.</param>
146+
/// <param name="settings">ScriptAnalyzer settings</param>
147+
/// <returns></returns>
148+
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file, Hashtable settings)
149+
{
150+
return GetSemanticMarkers<Hashtable>(file, null, settings);
163151
}
164152

165153
/// <summary>
@@ -187,6 +175,27 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
187175
return ruleNames;
188176
}
189177

178+
/// <summary>
179+
/// Construct a PSScriptAnalyzer settings hashtable
180+
/// </summary>
181+
/// <param name="ruleSettingsMap">A settings hashtable</param>
182+
/// <returns></returns>
183+
public Hashtable GetPSSASettingsHashtable(IDictionary<string, Hashtable> ruleSettingsMap)
184+
{
185+
var hashtable = new Hashtable();
186+
var ruleSettingsHashtable = new Hashtable();
187+
188+
hashtable["IncludeRules"] = ruleSettingsMap.Keys.ToArray<object>();
189+
hashtable["Rules"] = ruleSettingsHashtable;
190+
191+
foreach (var kvp in ruleSettingsMap)
192+
{
193+
ruleSettingsHashtable.Add(kvp.Key, kvp.Value);
194+
}
195+
196+
return hashtable;
197+
}
198+
190199
/// <summary>
191200
/// Disposes the runspace being used by the analysis service.
192201
/// </summary>
@@ -203,6 +212,42 @@ public void Dispose()
203212
#endregion // public methods
204213

205214
#region Private Methods
215+
216+
private ScriptFileMarker[] GetSemanticMarkers<TSettings>(
217+
ScriptFile file,
218+
string[] rules,
219+
TSettings settings) where TSettings : class
220+
{
221+
if (this.scriptAnalyzerModuleInfo != null
222+
&& file.IsAnalysisEnabled
223+
&& (typeof(TSettings) == typeof(string) || typeof(TSettings) == typeof(Hashtable))
224+
&& (rules != null || settings != null))
225+
{
226+
// TODO: This is a temporary fix until we can change how
227+
// ScriptAnalyzer invokes their async tasks.
228+
// TODO: Make this async
229+
Task<ScriptFileMarker[]> analysisTask =
230+
Task.Factory.StartNew<ScriptFileMarker[]>(
231+
() =>
232+
{
233+
return
234+
GetDiagnosticRecords(file, rules, settings)
235+
.Select(ScriptFileMarker.FromDiagnosticRecord)
236+
.ToArray();
237+
},
238+
CancellationToken.None,
239+
TaskCreationOptions.None,
240+
TaskScheduler.Default);
241+
analysisTask.Wait();
242+
return analysisTask.Result;
243+
}
244+
else
245+
{
246+
// Return an empty marker list
247+
return new ScriptFileMarker[0];
248+
}
249+
}
250+
206251
private void FindPSScriptAnalyzer()
207252
{
208253
lock (runspaceLock)
@@ -291,10 +336,22 @@ private void InitializePSScriptAnalyzer()
291336
}
292337

293338
private IEnumerable<PSObject> GetDiagnosticRecords(ScriptFile file)
339+
{
340+
return GetDiagnosticRecords(file, this.activeRules, this.settingsPath);
341+
}
342+
343+
// TSettings can either be of type Hashtable or string
344+
// as scriptanalyzer settings parameter takes either a hashtable or string
345+
private IEnumerable<PSObject> GetDiagnosticRecords<TSettings>(
346+
ScriptFile file,
347+
string[] rules,
348+
TSettings settings) where TSettings: class
294349
{
295350
IEnumerable<PSObject> diagnosticRecords = Enumerable.Empty<PSObject>();
296351

297-
if (this.scriptAnalyzerModuleInfo != null)
352+
if (this.scriptAnalyzerModuleInfo != null
353+
&& (typeof(TSettings) == typeof(string)
354+
|| typeof(TSettings) == typeof(Hashtable)))
298355
{
299356
lock (runspaceLock)
300357
{
@@ -310,13 +367,13 @@ private IEnumerable<PSObject> GetDiagnosticRecords(ScriptFile file)
310367
.AddParameter("ScriptDefinition", file.Contents);
311368

312369
// Use a settings file if one is provided, otherwise use the default rule list.
313-
if (!string.IsNullOrWhiteSpace(this.SettingsPath))
370+
if (settings != null)
314371
{
315-
powerShell.AddParameter("Settings", this.SettingsPath);
372+
powerShell.AddParameter("Settings", settings);
316373
}
317374
else
318375
{
319-
powerShell.AddParameter("IncludeRule", activeRules);
376+
powerShell.AddParameter("IncludeRule", rules);
320377
}
321378

322379
diagnosticRecords = powerShell.Invoke();

src/PowerShellEditorServices/Language/LanguageService.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55

66
using Microsoft.PowerShell.EditorServices.Utility;
77
using System;
8+
using System.Collections;
89
using System.Collections.Generic;
910
using System.Linq;
1011
using System.Management.Automation;
12+
using System.Management.Automation.Language;
1113
using System.Threading;
1214
using System.Threading.Tasks;
1315

@@ -132,7 +134,7 @@ await this.powerShellContext.GetRunspaceHandle(
132134
}
133135

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

@@ -163,7 +165,7 @@ public CompletionDetails GetCompletionDetailsInFile(
163165
/// <param name="lineNumber">The line number of the cursor for the given script</param>
164166
/// <param name="columnNumber">The coulumn number of the cursor for the given script</param>
165167
/// <returns>A SymbolReference of the symbol found at the given location
166-
/// or null if there is no symbol at that location
168+
/// or null if there is no symbol at that location
167169
/// </returns>
168170
public SymbolReference FindSymbolAtLocation(
169171
ScriptFile scriptFile,
@@ -255,7 +257,7 @@ public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile)
255257
public async Task<FindReferencesResult> FindReferencesOfSymbol(
256258
SymbolReference foundSymbol,
257259
ScriptFile[] referencedFiles)
258-
{
260+
{
259261
if (foundSymbol != null)
260262
{
261263
int symbolOffset = referencedFiles[0].GetOffsetAtPosition(
@@ -336,7 +338,7 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
336338
}
337339
}
338340

339-
// if definition is not found in referenced files
341+
// if definition is not found in referenced files
340342
// look for it in the builtin commands
341343
if (foundDefinition == null)
342344
{
@@ -345,9 +347,9 @@ await CommandHelpers.GetCommandInfo(
345347
foundSymbol.SymbolName,
346348
this.powerShellContext);
347349

348-
foundDefinition =
350+
foundDefinition =
349351
FindDeclarationForBuiltinCommand(
350-
cmdInfo,
352+
cmdInfo,
351353
foundSymbol,
352354
workspace);
353355
}
@@ -533,7 +535,7 @@ private ScriptFile[] GetBuiltinCommandScriptFiles(
533535
}
534536

535537
private SymbolReference FindDeclarationForBuiltinCommand(
536-
CommandInfo commandInfo,
538+
CommandInfo commandInfo,
537539
SymbolReference foundSymbol,
538540
Workspace workspace)
539541
{

src/PowerShellEditorServices/Nano.PowerShellEditorServices.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
<Compile Include="..\PowerShellEditorServices.Protocol\LanguageServer\GetPSSARulesRequest.cs">
4040
<Link>GetPSSARulesRequest.cs</Link>
4141
</Compile>
42+
<Compile Include="..\PowerShellEditorServices.Protocol\LanguageServer\ScriptFileMarkersRequest.cs">
43+
<Link>ScriptFileMarkersRequest.cs</Link>
44+
</Compile>
4245
<Compile Include="..\PowerShellEditorServices.Protocol\LanguageServer\SetPSSARulesRequest.cs">
4346
<Link>SetPSSARulesRequest.cs</Link>
4447
</Compile>

0 commit comments

Comments
 (0)