3131using System . Collections . ObjectModel ;
3232using System . Collections ;
3333using System . Diagnostics ;
34+ using System . Text ;
3435
3536namespace Microsoft . Windows . PowerShell . ScriptAnalyzer
3637{
@@ -1443,43 +1444,67 @@ public Dictionary<string, List<string>> CheckRuleExtension(string[] path, PathIn
14431444 return results ;
14441445 }
14451446
1446- #endregion
1447+ #endregion
14471448
14481449
14491450 /// <summary>
14501451 /// Analyzes a script file or a directory containing script files.
14511452 /// </summary>
14521453 /// <param name="path">The path of the file or directory to analyze.</param>
1454+ /// <param name="shouldProcess">Whether the action should be executed.</param>
14531455 /// <param name="searchRecursively">
14541456 /// If true, recursively searches the given file path and analyzes any
14551457 /// script files that are found.
14561458 /// </param>
14571459 /// <returns>An enumeration of DiagnosticRecords that were found by rules.</returns>
1458- public IEnumerable < DiagnosticRecord > AnalyzePath ( string path , bool searchRecursively = false )
1460+ public IEnumerable < DiagnosticRecord > AnalyzePath ( string path , Func < string , string , bool > shouldProcess , bool searchRecursively = false )
14591461 {
1460- List < string > scriptFilePaths = new List < string > ( ) ;
1462+ List < string > scriptFilePaths = ScriptPathList ( path , searchRecursively ) ;
14611463
1462- if ( path == null )
1464+ foreach ( string scriptFilePath in scriptFilePaths )
14631465 {
1464- this . outputWriter . ThrowTerminatingError (
1465- new ErrorRecord (
1466- new FileNotFoundException ( ) ,
1467- string . Format ( CultureInfo . CurrentCulture , Strings . FileNotFound , path ) ,
1468- ErrorCategory . InvalidArgument ,
1469- this ) ) ;
1466+ if ( shouldProcess ( scriptFilePath , $ "Analyzing file { scriptFilePath } ") )
1467+ {
1468+ // Yield each record in the result so that the caller can pull them one at a time
1469+ foreach ( var diagnosticRecord in this . AnalyzeFile ( scriptFilePath ) )
1470+ {
1471+ yield return diagnosticRecord ;
1472+ }
1473+ }
14701474 }
1475+ }
1476+
1477+ /// <summary>
1478+ /// Analyzes a script file or a directory containing script files and fixes warning where possible.
1479+ /// </summary>
1480+ /// <param name="path">The path of the file or directory to analyze.</param>
1481+ /// <param name="shouldProcess">Whether the action should be executed.</param>
1482+ /// <param name="searchRecursively">
1483+ /// If true, recursively searches the given file path and analyzes any
1484+ /// script files that are found.
1485+ /// </param>
1486+ /// <returns>An enumeration of DiagnosticRecords that were found by rules and could not be fixed automatically.</returns>
1487+ public IEnumerable < DiagnosticRecord > AnalyzeAndFixPath ( string path , Func < string , string , bool > shouldProcess , bool searchRecursively = false )
1488+ {
1489+ List < string > scriptFilePaths = ScriptPathList ( path , searchRecursively ) ;
14711490
1472- // Create in advance the list of script file paths to analyze. This
1473- // is an optimization over doing the whole operation at once
1474- // and calling .Concat on IEnumerables to join results.
1475- this . BuildScriptPathList ( path , searchRecursively , scriptFilePaths ) ;
14761491 foreach ( string scriptFilePath in scriptFilePaths )
14771492 {
1478- // Yield each record in the result so that the
1479- // caller can pull them one at a time
1480- foreach ( var diagnosticRecord in this . AnalyzeFile ( scriptFilePath ) )
1493+ if ( shouldProcess ( scriptFilePath , $ "Analyzing and fixing file { scriptFilePath } ") )
14811494 {
1482- yield return diagnosticRecord ;
1495+ var fileEncoding = GetFileEncoding ( scriptFilePath ) ;
1496+ bool fixesWereApplied ;
1497+ var scriptFileContentWithFixes = Fix ( File . ReadAllText ( scriptFilePath , fileEncoding ) , out fixesWereApplied ) ;
1498+ if ( fixesWereApplied )
1499+ {
1500+ File . WriteAllText ( scriptFilePath , scriptFileContentWithFixes , fileEncoding ) ;
1501+ }
1502+
1503+ // Yield each record in the result so that the caller can pull them one at a time
1504+ foreach ( var diagnosticRecord in this . AnalyzeFile ( scriptFilePath ) )
1505+ {
1506+ yield return diagnosticRecord ;
1507+ }
14831508 }
14841509 }
14851510 }
@@ -1531,16 +1556,17 @@ public IEnumerable<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefini
15311556 /// Fix the violations in the given script text.
15321557 /// </summary>
15331558 /// <param name="scriptDefinition">The script text to be fixed.</param>
1559+ /// <param name="updatedRange">Whether any warnings were fixed.</param>
15341560 /// <returns>The fixed script text.</returns>
1535- public string Fix ( string scriptDefinition )
1561+ public string Fix ( string scriptDefinition , out bool fixesWereApplied )
15361562 {
15371563 if ( scriptDefinition == null )
15381564 {
15391565 throw new ArgumentNullException ( nameof ( scriptDefinition ) ) ;
15401566 }
15411567
15421568 Range updatedRange ;
1543- return Fix ( new EditableText ( scriptDefinition ) , null , out updatedRange ) . ToString ( ) ;
1569+ return Fix ( new EditableText ( scriptDefinition ) , null , out updatedRange , out fixesWereApplied ) . ToString ( ) ;
15441570 }
15451571
15461572 /// <summary>
@@ -1549,8 +1575,9 @@ public string Fix(string scriptDefinition)
15491575 /// <param name="text">An object of type `EditableText` that encapsulates the script text to be fixed.</param>
15501576 /// <param name="range">The range in which the fixes are allowed.</param>
15511577 /// <param name="updatedRange">The updated range after the fixes have been applied.</param>
1578+ /// <param name="updatedRange">Whether any warnings were fixed.</param>
15521579 /// <returns>The same instance of `EditableText` that was passed to the method, but the instance encapsulates the fixed script text. This helps in chaining the Fix method.</returns>
1553- public EditableText Fix ( EditableText text , Range range , out Range updatedRange )
1580+ public EditableText Fix ( EditableText text , Range range , out Range updatedRange , out bool fixesWereApplied )
15541581 {
15551582 if ( text == null )
15561583 {
@@ -1583,7 +1610,9 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange)
15831610 this . outputWriter . WriteVerbose ( $ "Found { corrections . Count } violations.") ;
15841611 int unusedCorrections ;
15851612 Fix ( text , corrections , out unusedCorrections ) ;
1586- this . outputWriter . WriteVerbose ( $ "Fixed { corrections . Count - unusedCorrections } violations.") ;
1613+ var numberOfFixedViolatons = corrections . Count - unusedCorrections ;
1614+ fixesWereApplied = numberOfFixedViolatons > 0 ;
1615+ this . outputWriter . WriteVerbose ( $ "Fixed { numberOfFixedViolatons } violations.") ;
15871616
15881617 // This is an indication of an infinite loop. There is a small chance of this.
15891618 // It is better to abort the fixing operation at this point.
@@ -1613,6 +1642,18 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange)
16131642 return text ;
16141643 }
16151644
1645+ private static Encoding GetFileEncoding ( string path )
1646+ {
1647+ using ( var stream = new FileStream ( path , FileMode . Open ) )
1648+ {
1649+ using ( var reader = new StreamReader ( stream ) )
1650+ {
1651+ reader . Peek ( ) ; // needed in order to populate the CurrentEncoding property
1652+ return reader . CurrentEncoding ;
1653+ }
1654+ }
1655+ }
1656+
16161657 private static Range SnapToEdges ( EditableText text , Range range )
16171658 {
16181659 // todo add TextLines.Validate(range) and TextLines.Validate(position)
@@ -1655,6 +1696,28 @@ private static EditableText Fix(
16551696 return text ;
16561697 }
16571698
1699+ private List < string > ScriptPathList ( string path , bool searchRecursively )
1700+ {
1701+ List < string > scriptFilePaths = new List < string > ( ) ;
1702+
1703+ if ( path == null )
1704+ {
1705+ this . outputWriter . ThrowTerminatingError (
1706+ new ErrorRecord (
1707+ new FileNotFoundException ( ) ,
1708+ string . Format ( CultureInfo . CurrentCulture , Strings . FileNotFound , path ) ,
1709+ ErrorCategory . InvalidArgument ,
1710+ this ) ) ;
1711+ }
1712+
1713+ // Create in advance the list of script file paths to analyze. This
1714+ // is an optimization over doing the whole operation at once
1715+ // and calling .Concat on IEnumerables to join results.
1716+ this . BuildScriptPathList ( path , searchRecursively , scriptFilePaths ) ;
1717+
1718+ return scriptFilePaths ;
1719+ }
1720+
16581721 private void BuildScriptPathList (
16591722 string path ,
16601723 bool searchRecursively ,
0 commit comments