Skip to content
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

Merged
merged 10 commits into from
Feb 11, 2019

Conversation

JamesWTruher
Copy link
Member

@JamesWTruher JamesWTruher commented Jan 17, 2019

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

…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);
Copy link
Member

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?

Copy link
Member Author

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

@bergmeister
Copy link
Collaborator

bergmeister commented Jan 23, 2019

From a high level this makes sense. There are a few tests to be fixed/looked at first.
Can one suppress those special diagnostic records? Does this work together with the -Severity parameter on Invoke-ScriptAnalyzer?
I'll do a proper review once the PR is ready.

Some tests are checking ErrorVariable for parse errors which are now part of the output stream
@JamesWTruher JamesWTruher changed the title WIP: Emit parsing errors as diagnostic records Emit parsing errors as diagnostic records Feb 7, 2019
@JamesWTruher
Copy link
Member Author

@bergmeister - suggestions for documentation additions?

@@ -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");
Copy link
Collaborator

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed!

@bergmeister
Copy link
Collaborator

bergmeister commented Feb 7, 2019

Looks very nice. I also checked how it looks with the integration into VS-Code and it looks so much better. But should we maybe prepend ParseError: to it?
image

@bergmeister
Copy link
Collaborator

@JamesWTruher How would people suppress it in vscode (where no command line exists and most people do not use settings files)
image

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)
Copy link
Contributor

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:

Suggested change
if ( relevantParseErrors != null && emitParseErrors == 1)
if ( relevantParseErrors != null && (severity == null || severity.Contains("ParseError", StringComparer.OrdinalIgnoreCase)))

Copy link
Member Author

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

parseError.Extent,
parseError.ErrorId.ToString(),
DiagnosticSeverity.ParseError,
"" // no script file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"" // no script file
string.Empty // no script file

{
foreach (var parseError in relevantParseErrors)
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
var results = new List<DiagnosticRecord>();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

{
foreach (var parseError in relevantParseErrors)
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
foreach ( var parseError in relevantParseErrors )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach ( var parseError in relevantParseErrors )
foreach (ParseError parseError in relevantParseErrors)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjmholt fixed

@@ -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)
Copy link
Contributor

@rjmholt rjmholt Feb 7, 2019

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:

Suggested change
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.

Copy link
Contributor

@rjmholt rjmholt Feb 7, 2019

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.

Copy link
Member Author

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

scriptAst = Parser.ParseFile(filePath, out scriptTokens, out errors);
}
#endif //!PSV3
var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords);
IEnumerable<ParseError> relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords);


// 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 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
var results = new List<DiagnosticRecord>();

if ( relevantParseErrors != null && emitParseErrors == 1 )
{
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
foreach ( var parseError in relevantParseErrors )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach ( var parseError in relevantParseErrors )
foreach (ParseError parseError in relevantParseErrors)

@rjmholt
Copy link
Contributor

rjmholt commented Feb 7, 2019

How would people suppress it in vscode (where no command line exists and most people do not use settings files)

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.

@bergmeister
Copy link
Collaborator

@rjmholt Thanks for the info, that's good to know, I agree, lets keep it how it is, PSES can keep its flight computer ;-)

Copy link
Collaborator

@bergmeister bergmeister left a 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
@kilasuit
Copy link
Contributor

LGTM

@JamesWTruher
Copy link
Member Author

I've looked at implementing support for [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute] and while it's possible, it doesn't work like I want. (the bare attribute can't exist without a param statement). I could also do something like #pragma, etc, but I haven't looked at that. For the moment, -Severity can be set to Warning,Error,Information which will suppress the output.

@JamesWTruher JamesWTruher merged commit c9915b1 into PowerShell:development Feb 11, 2019
@bergmeister
Copy link
Collaborator

Sounds good. We have a separate issue to introduce pragma like region or line based suppression anyway

@plastikfan
Copy link

How would people suppress it in vscode (where no command line exists and most people do not use settings files)

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.

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.

@rjmholt
Copy link
Contributor

rjmholt commented Sep 9, 2020

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

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:

  • Open a feature request on the PowerShell extension for VSCode to suppress parser errors
  • Change the way you handle these scripts in some way (for example, marking them as template files)

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.

@kilasuit
Copy link
Contributor

kilasuit commented Sep 9, 2020

What is PSES?

@plastikfan it is PowerShell Editor Services - https://github.com/PowerShell/PowerShellEditorServices

@plastikfan
Copy link

* Open a feature request on the PowerShell extension for VSCode to suppress parser errors

* Change the way you handle these scripts in some way (for example, marking them as template files)

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.

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?

@rjmholt
Copy link
Contributor

rjmholt commented Sep 9, 2020

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.

@plastikfan
Copy link

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!

@JustinGrote
Copy link

@rjmholt Was an issue ever opened for this? Can we reference it here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TypeNotFound suppressions
7 participants