-
Notifications
You must be signed in to change notification settings - Fork 388
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Emit parsing errors as diagnostic records #1130
Emit parsing errors as diagnostic records #1130
Conversation
…r than emitting error records Embedded tools such as VSCode have real difficulty when there are parser errors. We need to be sure to just return what we find and let the down stream consumers filter.
I've noticed it's sometimes hard to determine went wrong in the build, this may help a bit
filePath) | ||
); | ||
} | ||
diagnosticRecords.AddRange(results); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you add all the results to a temp array and then use AddRange
instead of just adding directly to diagnosticRecords
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
earlier debugging processes allowed me to see the new parser errors more easily
From a high level this makes sense. There are a few tests to be fixed/looked at first. |
Some tests are checking ErrorVariable for parse errors which are now part of the output stream
84ace38
to
edaca8e
Compare
All findings are returned by default, but if you exclude ParseErrors in -Severity we won't emit them.
Force array of output
@bergmeister - suggestions for documentation additions? |
Engine/ScriptAnalyzer.cs
Outdated
@@ -1526,23 +1526,27 @@ public IEnumerable<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefini | |||
|
|||
var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out List<DiagnosticRecord> diagnosticRecords); | |||
|
|||
if (relevantParseErrors != null && relevantParseErrors.Count > 0) | |||
int emitParseErrors = severity == null ? 1 : severity.Count(item => item == "ParseError"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
converting this to a boolean and using Any()
instead of .Count()
would be better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed!
@JamesWTruher How would people suppress it in vscode (where no command line exists and most people do not use settings files) |
Engine/ScriptAnalyzer.cs
Outdated
if (relevantParseErrors != null && relevantParseErrors.Count > 0) | ||
int emitParseErrors = severity == null ? 1 : severity.Count(item => item == "ParseError"); | ||
// Add parse errors first if requested! | ||
if ( relevantParseErrors != null && emitParseErrors == 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than create the variable above which seems to be a boolean in wolf's clothing, I'd just inline the test here:
if ( relevantParseErrors != null && emitParseErrors == 1) | |
if ( relevantParseErrors != null && (severity == null || severity.Contains("ParseError", StringComparer.OrdinalIgnoreCase))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rjmholt I made those changes
Engine/ScriptAnalyzer.cs
Outdated
parseError.Extent, | ||
parseError.ErrorId.ToString(), | ||
DiagnosticSeverity.ParseError, | ||
"" // no script file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"" // no script file | |
string.Empty // no script file |
Engine/ScriptAnalyzer.cs
Outdated
{ | ||
foreach (var parseError in relevantParseErrors) | ||
List<DiagnosticRecord> results = new List<DiagnosticRecord>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<DiagnosticRecord> results = new List<DiagnosticRecord>(); | |
var results = new List<DiagnosticRecord>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
Engine/ScriptAnalyzer.cs
Outdated
{ | ||
foreach (var parseError in relevantParseErrors) | ||
List<DiagnosticRecord> results = new List<DiagnosticRecord>(); | ||
foreach ( var parseError in relevantParseErrors ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foreach ( var parseError in relevantParseErrors ) | |
foreach (ParseError parseError in relevantParseErrors) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rjmholt fixed
Engine/ScriptAnalyzer.cs
Outdated
@@ -1890,6 +1853,51 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath) | |||
return null; | |||
} | |||
|
|||
// short-circuited processing for a help file | |||
// no parsing can really be done, but there are other rules to run (specifically encoding). | |||
if ( Regex.Matches(Path.GetFileName(filePath), @"^about_.*help.txt$", RegexOptions.IgnoreCase).Count != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This regex might benefit from being a compiled private readonly static
field:
private readonly Regex s_aboutHelpRegex = new Regex("^about_.*help\\.txt$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Then here:
if ( Regex.Matches(Path.GetFileName(filePath), @"^about_.*help.txt$", RegexOptions.IgnoreCase).Count != 0) | |
if (s_aboutHelpRegex.IsMatch(Path.GetFileName(filePath))) |
EDIT: Put a backlash in for the .
before txt
to distinguish it from the regex wildcard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or the other possibility is just to do:
string fileName = Path.GetFileName(filePath);
if (fileName.StartsWith("about_", StringComparison.OrdinalIgnoreCase)
&& fileName.EndsWith("help.txt", StringComparison.OrdinalIgnoreCase))
{
...
}
This is probably the most efficient option, since it doesn't even need to read the whole string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with the regex
Engine/ScriptAnalyzer.cs
Outdated
scriptAst = Parser.ParseFile(filePath, out scriptTokens, out errors); | ||
} | ||
#endif //!PSV3 | ||
var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords); | |
IEnumerable<ParseError> relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords); |
Engine/ScriptAnalyzer.cs
Outdated
|
||
// First, add all parse errors if they've been requested | ||
int emitParseErrors = severity == null ? 1 : severity.Count(item => item == "ParseError"); | ||
if ( relevantParseErrors != null && emitParseErrors == 1 ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ( relevantParseErrors != null && emitParseErrors == 1 ) | |
if (relevantParseErrors != null && (severity == null || severity.Contains("ParseError", StringComparer.OrdinalIgnoreCase))) |
int emitParseErrors = severity == null ? 1 : severity.Count(item => item == "ParseError"); | ||
if ( relevantParseErrors != null && emitParseErrors == 1 ) | ||
{ | ||
List<DiagnosticRecord> results = new List<DiagnosticRecord>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<DiagnosticRecord> results = new List<DiagnosticRecord>(); | |
var results = new List<DiagnosticRecord>(); |
Engine/ScriptAnalyzer.cs
Outdated
if ( relevantParseErrors != null && emitParseErrors == 1 ) | ||
{ | ||
List<DiagnosticRecord> results = new List<DiagnosticRecord>(); | ||
foreach ( var parseError in relevantParseErrors ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foreach ( var parseError in relevantParseErrors ) | |
foreach (ParseError parseError in relevantParseErrors) |
The red squiggly in VSCode comes from PSES, not PSScriptAnalyzer, because those are legitimate parse errors. I'm not sure if we currently have a way of suppressing them. Installing the PowerShell extension and then suppressing parse errors would be like buying a fighter jet and ripping out the flight computer. |
@rjmholt Thanks for the info, that's good to know, I agree, lets keep it how it is, PSES can keep its flight computer ;-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments, and maybe add a few lines into the main readme to explain this new type and that one does not suppress it like PSSA rules.
Otherwise it looks great, feel free to merge afterwards
Update documentation to include new behavior for ParseErrors and fix up logic to be a bit cleaner when emiting ParseErrors
LGTM |
I've looked at implementing support for |
Sounds good. We have a separate issue to introduce pragma like region or line based suppression anyway |
What is PSES? I am experiencing this error in relation to PowerShell class inside a PowerShell module and although my code is correct (as a result of concatenating all source files into a combined .psm1 file), VSC is still flagging in the original source. This is rather unfortunate because I'm not sure how to disable the flagging of this non-error if it isn't being generated by PSSA built into VSC. |
If you run the PowerShell parser itself on your file, it will raise this error. The PowerShell VSCode extension just reports that. There's no way to suppress a parser error currently, because parser errors are the most serious kind of error. This is to say that what you're seeing isn't an error in any of the tooling -- it's a mismatch between how you're handling the file and what PowerShell expects. The two options I can imagine are:
The script as you have it won't run as a standalone. That's what the PowerShell extension and all the other tools assess currently. |
@plastikfan it is PowerShell Editor Services - https://github.com/PowerShell/PowerShellEditorServices |
I've decided that the best way forward is to constrain all class references to a single file. So I currently have the need for 2 classes. Instead of implementing them in separate files, I am going to implement them all in the same file including defining a factory function to create the class instances: controller.ps1: class Controller {
[System.Collections.Hashtable]$_passThru;
Controller(
[System.Collections.Hashtable]$passThru
) {
$this._passThru = $passThru;
}
}
class ForeachController : Controller {
ForeachController(
[System.Collections.Hashtable]$passThru
) {
}
}
function New-Controller {
# return appropriate new instance
} No other module code will make explicit reference to classes. This is the best way I can see going forweard for the time being. It's less than desirable, but at least I don't have to put up with none-errors being reported by VSC and potentially hiding real issues. So @rjmholt, when you say raise a feature request against the powershell extension for VScode, is this https://github.com/PowerShell/vscode-powershell or if the error is being emitted from PSES, shouldn't I submit to that instead? |
Almost all the issues in the vscode-powershell repo are actually things implemented in PSES, but people were confused by that distinction and redirecting them just increased friction. So you're free to open the issue wherever, but we have more set up to track it properly in vscode-powershell. |
Actually on second thoughts, having chatted some folks on POwerShell group on Gitter, I've decided to avoid using POwerShell classes altogther because there are potentially lots of gotchyas. I'm going to resort to attaching functions to PSCustomObjects and just be done with it. As much as I love PowerShell, the number of gotchyas in it are infuriating, so I'd rather just avoid classes which I have not used in PowerShell up to this point. I could do without the grief! |
@rjmholt Was an issue ever opened for this? Can we reference it here? |
PR Summary
Fixes #1041
Emit parsing errors as diagnostic records.
I'm attempting to bring analyzer into a more lint like behavior which treats parsing/syntax errors as just another type of diagnostic.
Currently, errors during parsing are written as error records, but they should probably just be diagnostic records. This PR creates a new diagnostic record type and emits errors found during parsing into the result stream. This is far more useful to downstream readers (such as VSCode/EditorServices) because there's a single stream for information about the script. Rule violations and syntax errors.
I have also removed the current limitation of any time there are more than 10 parse errors, the analyzer simply returns, which seems to me to be pretty undesirable. We will now emit diagnostic records for all parse errors.
PR Checklist
.cs
,.ps1
and.psm1
files have the correct copyright headerWIP:
to the beginning of the title and remove the prefix when the PR is ready.