diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 6a7a6ea20..1c6a64fff 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1522,16 +1522,18 @@ public IEnumerable AnalyzeScriptDefinition(string scriptDefini return null; } - if (errors != null && errors.Length > 0) + var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out List diagnosticRecords); + + if (relevantParseErrors != null && relevantParseErrors.Count > 0) { - foreach (ParseError error in errors) + foreach (var parseError in relevantParseErrors) { - string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParseErrorFormatForScriptDefinition, error.Message.TrimEnd('.'), error.Extent.StartLineNumber, error.Extent.StartColumnNumber); - this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, error.ErrorId)); + string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParseErrorFormatForScriptDefinition, parseError.Message.TrimEnd('.'), parseError.Extent.StartLineNumber, parseError.Extent.StartColumnNumber); + this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, parseError.ErrorId)); } } - if (errors != null && errors.Length > 10) + if (relevantParseErrors != null && relevantParseErrors.Count > 10) { string manyParseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorMessageForScriptDefinition); this.outputWriter.WriteError(new ErrorRecord(new ParseException(manyParseErrorMessage), manyParseErrorMessage, ErrorCategory.ParserError, scriptDefinition)); @@ -1539,7 +1541,7 @@ public IEnumerable AnalyzeScriptDefinition(string scriptDefini return new List(); } - return this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty); + return diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty)); } /// @@ -1644,6 +1646,33 @@ private static Encoding GetFileEncoding(string path) } } + /// + /// Inspects Parse errors and removes TypeNotFound errors that can be ignored since some types are not known yet (e.g. due to 'using' statements). + /// + /// + /// List of relevant parse errors. + private List RemoveTypeNotFoundParseErrors(ParseError[] parseErrors, out List diagnosticRecords) + { + var relevantParseErrors = new List(); + diagnosticRecords = new List(); + + foreach (var parseError in parseErrors) + { + // If types are not known due them not being imported yet, the parser throws an error that can be ignored + if (parseError.ErrorId != "TypeNotFound") + { + relevantParseErrors.Add(parseError); + } + else + { + diagnosticRecords.Add(new DiagnosticRecord( + string.Format(Strings.TypeNotFoundParseErrorFound, parseError.Extent), parseError.Extent, "TypeNotFound", DiagnosticSeverity.Information, parseError.Extent.File)); + } + } + + return relevantParseErrors; + } + private static Range SnapToEdges(EditableText text, Range range) { // todo add TextLines.Validate(range) and TextLines.Validate(position) @@ -1806,6 +1835,7 @@ private IEnumerable AnalyzeFile(string filePath) ParseError[] errors = null; this.outputWriter.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseFileMessage, filePath)); + var diagnosticRecords = new List(); //Parse the file if (File.Exists(filePath)) @@ -1829,17 +1859,19 @@ private IEnumerable AnalyzeFile(string filePath) scriptAst = Parser.ParseFile(filePath, out scriptTokens, out errors); } #endif //!PSV3 + var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords); + //Runspace.DefaultRunspace = oldDefault; - if (errors != null && errors.Length > 0) + if (relevantParseErrors != null && relevantParseErrors.Count > 0) { - foreach (ParseError error in errors) + foreach (var parseError in relevantParseErrors) { - string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorFormat, error.Extent.File, error.Message.TrimEnd('.'), error.Extent.StartLineNumber, error.Extent.StartColumnNumber); - this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, error.ErrorId)); + string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorFormat, parseError.Extent.File, parseError.Message.TrimEnd('.'), parseError.Extent.StartLineNumber, parseError.Extent.StartColumnNumber); + this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, parseError.ErrorId)); } } - if (errors != null && errors.Length > 10) + if (relevantParseErrors != null && relevantParseErrors.Count > 10) { string manyParseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorMessage, System.IO.Path.GetFileName(filePath)); this.outputWriter.WriteError(new ErrorRecord(new ParseException(manyParseErrorMessage), manyParseErrorMessage, ErrorCategory.ParserError, filePath)); @@ -1856,7 +1888,7 @@ private IEnumerable AnalyzeFile(string filePath) return null; } - return this.AnalyzeSyntaxTree(scriptAst, scriptTokens, filePath); + return diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, filePath)); } private bool IsModuleNotFoundError(ParseError error) diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index d7234f283..077b3a591 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -10,7 +10,6 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { using System; - using System.Reflection; /// @@ -40,7 +39,7 @@ internal Strings() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Windows.PowerShell.ScriptAnalyzer.Strings", typeof(Strings).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Windows.PowerShell.ScriptAnalyzer.Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; @@ -592,6 +591,15 @@ internal static string TextLinesNoNullItem { } } + /// + /// Looks up a localized string similar to Ignoring 'TypeNotFound' parse error on type '{0}'. Check if the specified type is correct. This can also be due the type not being known at parse time due to types imported by 'using' statements.. + /// + internal static string TypeNotFoundParseErrorFound { + get { + return ResourceManager.GetString("TypeNotFoundParseErrorFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Analyzing file: {0}. /// diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 99d90761f..743c3ccad 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -324,4 +324,7 @@ Settings object could not be resolved. + + Ignoring 'TypeNotFound' parse error on type '{0}'. Check if the specified type is correct. This can also be due the type not being known at parse time due to types imported by 'using' statements. + \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config index d9071f66f..acba4478b 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,7 +3,6 @@ - diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index c6e5b9b2a..049b0f682 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -551,4 +551,29 @@ Describe "Test -EnableExit Switch" { "$result" | Should -Not -BeLike $reportSummaryFor1Warning } } + + # using statements are only supported in v5+ + if (!$testingLibraryUsage -and ($PSVersionTable.PSVersion -ge [Version]'5.0.0')) { + Describe "Handles parse errors due to unknown types" { + $script = @' + using namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels + using namespace Microsoft.Azure.Commands.Common.Authentication.Abstractions + Import-Module "AzureRm" + class MyClass { [IStorageContext]$StorageContext } # This will result in a parser error due to [IStorageContext] type that comes from the using statement but is not known at parse time +'@ + It "does not throw and detect one expected warning after the parse error has occured when using -ScriptDefintion parameter set" { + $warnings = Invoke-ScriptAnalyzer -ScriptDefinition $script + $warnings.Count | Should -Be 1 + $warnings.RuleName | Should -Be 'TypeNotFound' + } + + $testFilePath = "TestDrive:\testfile.ps1" + Set-Content $testFilePath -value $script + It "does not throw and detect one expected warning after the parse error has occured when using -Path parameter set" { + $warnings = Invoke-ScriptAnalyzer -Path $testFilePath + $warnings.Count | Should -Be 1 + $warnings.RuleName | Should -Be 'TypeNotFound' + } + } + } } diff --git a/tools/appveyor.psm1 b/tools/appveyor.psm1 index fca49b807..0625dea33 100644 --- a/tools/appveyor.psm1 +++ b/tools/appveyor.psm1 @@ -44,7 +44,7 @@ function Invoke-AppVeyorBuild { $BuildType, [Parameter(Mandatory)] - [ValidateSet('Release', 'PSv4Release', 'PSv4Release')] + [ValidateSet('Release', 'PSv3Release', 'PSv4Release')] $BuildConfiguration, [Parameter(Mandatory)]