@@ -1323,156 +1323,177 @@ internal List<RuleSuppression> GetSuppressionsConfiguration(ConfigurationDefinit
13231323 /// Suppress the rules from the diagnostic records list.
13241324 /// Returns a list of suppressed records as well as the ones that are not suppressed
13251325 /// </summary>
1326- /// <param name="ruleSuppressions"></param>
1327- /// <param name="diagnostics"></param>
1326+ /// <param name="ruleName">The name of the rule possibly being suppressed.</param>
1327+ /// <param name="ruleSuppressionsDict">A lookup table from rule name to suppressions of that rule.</param>
1328+ /// <param name="diagnostics">The list of all diagnostics emitted by the given rule.</param>
1329+ /// <param name="errors">Any errors that arise due to misconfigured suppression settings.</param>
1330+ /// <returns>A pair, with the first item being the list of suppressed diagnostics, and the second the list of unsuppressed diagnostics.</returns>
13281331 public Tuple < List < SuppressedRecord > , List < DiagnosticRecord > > SuppressRule (
13291332 string ruleName ,
13301333 Dictionary < string , List < RuleSuppression > > ruleSuppressionsDict ,
13311334 List < DiagnosticRecord > diagnostics ,
1332- out List < ErrorRecord > errorRecords )
1335+ out List < ErrorRecord > errors )
13331336 {
1334- List < SuppressedRecord > suppressedRecords = new List < SuppressedRecord > ( ) ;
1335- List < DiagnosticRecord > unSuppressedRecords = new List < DiagnosticRecord > ( ) ;
1336- Tuple < List < SuppressedRecord > , List < DiagnosticRecord > > result = Tuple . Create ( suppressedRecords , unSuppressedRecords ) ;
1337- errorRecords = new List < ErrorRecord > ( ) ;
1338- if ( diagnostics == null || diagnostics . Count == 0 )
1337+ var suppressedRecords = new List < SuppressedRecord > ( ) ;
1338+ var unsuppressedRecords = new List < DiagnosticRecord > ( ) ;
1339+ errors = new List < ErrorRecord > ( ) ;
1340+ var result = new Tuple < List < SuppressedRecord > , List < DiagnosticRecord > > ( suppressedRecords , unsuppressedRecords ) ;
1341+
1342+ if ( diagnostics is null || diagnostics . Count == 0 )
13391343 {
13401344 return result ;
13411345 }
13421346
1343- if ( ruleSuppressionsDict == null || ! ruleSuppressionsDict . ContainsKey ( ruleName )
1344- || ruleSuppressionsDict [ ruleName ] . Count == 0 )
1347+ if ( ruleSuppressionsDict is null
1348+ || ! ruleSuppressionsDict . TryGetValue ( ruleName , out List < RuleSuppression > ruleSuppressions )
1349+ || ruleSuppressions . Count == 0 )
13451350 {
1346- unSuppressedRecords . AddRange ( diagnostics ) ;
1351+ unsuppressedRecords . AddRange ( diagnostics ) ;
13471352 return result ;
13481353 }
13491354
1350- List < RuleSuppression > ruleSuppressions = ruleSuppressionsDict [ ruleName ] ;
1351- var offsetArr = GetOffsetArray ( diagnostics ) ;
1352- int recordIndex = 0 ;
1353- int startRecord = 0 ;
1354- bool [ ] suppressed = new bool [ diagnostics . Count ] ;
1355- foreach ( RuleSuppression ruleSuppression in ruleSuppressions )
1355+ // We need to report errors for any rule suppressions with IDs that are not applied to anything
1356+ // Start by assuming all suppressions are unapplied and then we'll remove them as we apply them later
1357+ var unappliedSuppressions = new HashSet < RuleSuppression > ( ) ;
1358+ foreach ( RuleSuppression suppression in ruleSuppressions )
13561359 {
1357- int suppressionCount = 0 ;
1358- while ( startRecord < diagnostics . Count
1359- // && diagnostics[startRecord].Extent.StartOffset < ruleSuppression.StartOffset)
1360- // && diagnostics[startRecord].Extent.StartLineNumber < ruleSuppression.st)
1361- && offsetArr [ startRecord ] != null && offsetArr [ startRecord ] . Item1 < ruleSuppression . StartOffset )
1360+ if ( ! string . IsNullOrWhiteSpace ( suppression . RuleSuppressionID ) )
13621361 {
1363- startRecord += 1 ;
1362+ unappliedSuppressions . Add ( suppression ) ;
13641363 }
1365-
1366- // at this point, start offset of startRecord is greater or equals to rulesuppression.startoffset
1367- recordIndex = startRecord ;
1368-
1369- while ( recordIndex < diagnostics . Count )
1364+ }
1365+
1366+ // We have suppressions and diagnostics
1367+ // For each diagnostic, collect all the suppressions that apply to it
1368+ // and if there are any, record the diagnostic as suppressed
1369+ foreach ( DiagnosticRecord diagnostic in diagnostics )
1370+ {
1371+ Tuple < int , int > offsetPair = GetOffset ( diagnostic ) ;
1372+
1373+ var appliedSuppressions = new List < RuleSuppression > ( ) ;
1374+ foreach ( RuleSuppression suppression in ruleSuppressions )
13701375 {
1371- DiagnosticRecord record = diagnostics [ recordIndex ] ;
1372- var curOffset = offsetArr [ recordIndex ] ;
1373-
1374- //if (record.Extent.EndOffset > ruleSuppression.EndOffset)
1375- if ( curOffset != null && curOffset . Item2 > ruleSuppression . EndOffset )
1376+ // Check that the diagnostic is within the suppressed extent, if we have such an extent
1377+ if ( ! ( offsetPair is null )
1378+ && ( offsetPair . Item1 < suppression . StartOffset || offsetPair . Item2 > suppression . EndOffset ) )
13761379 {
1377- break ;
1380+ continue ;
13781381 }
1379-
1380- // we suppress if there is no suppression id or if there is suppression id and it matches
1381- if ( string . IsNullOrWhiteSpace ( ruleSuppression . RuleSuppressionID )
1382- || ( ! String . IsNullOrWhiteSpace ( record . RuleSuppressionID ) &&
1383- string . Equals ( ruleSuppression . RuleSuppressionID , record . RuleSuppressionID , StringComparison . OrdinalIgnoreCase ) ) )
1382+
1383+ // If there's a rule suppression ID, check that it matches the diagnostic
1384+ if ( ! string . IsNullOrWhiteSpace ( suppression . RuleSuppressionID )
1385+ && ! string . Equals ( diagnostic . RuleSuppressionID , suppression . RuleSuppressionID , StringComparison . OrdinalIgnoreCase ) )
13841386 {
1385- suppressed [ recordIndex ] = true ;
1386- suppressedRecords . Add ( new SuppressedRecord ( record , ruleSuppression ) ) ;
1387- suppressionCount += 1 ;
1387+ continue ;
13881388 }
1389-
1390- recordIndex += 1 ;
1389+
1390+ unappliedSuppressions . Remove ( suppression ) ;
1391+ appliedSuppressions . Add ( suppression ) ;
13911392 }
1392-
1393- // If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
1394- if ( ! String . IsNullOrWhiteSpace ( ruleSuppression . RuleSuppressionID ) && suppressionCount == 0 )
1393+
1394+ if ( appliedSuppressions . Count > 0 )
13951395 {
1396- // checks whether are given a string or a file path
1397- if ( String . IsNullOrWhiteSpace ( diagnostics . First ( ) . Extent . File ) )
1398- {
1399- ruleSuppression . Error = String . Format ( CultureInfo . CurrentCulture , Strings . RuleSuppressionErrorFormatScriptDefinition , ruleSuppression . StartAttributeLine ,
1400- String . Format ( Strings . RuleSuppressionIDError , ruleSuppression . RuleSuppressionID ) ) ;
1401- }
1402- else
1403- {
1404- ruleSuppression . Error = String . Format ( CultureInfo . CurrentCulture , Strings . RuleSuppressionErrorFormat , ruleSuppression . StartAttributeLine ,
1405- System . IO . Path . GetFileName ( diagnostics . First ( ) . Extent . File ) , String . Format ( Strings . RuleSuppressionIDError , ruleSuppression . RuleSuppressionID ) ) ;
1406- }
1407- errorRecords . Add ( new ErrorRecord ( new ArgumentException ( ruleSuppression . Error ) , ruleSuppression . Error , ErrorCategory . InvalidArgument , ruleSuppression ) ) ;
1408- //this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
1396+ suppressedRecords . Add ( new SuppressedRecord ( diagnostic , appliedSuppressions ) ) ;
1397+ }
1398+ else
1399+ {
1400+ unsuppressedRecords . Add ( diagnostic ) ;
14091401 }
14101402 }
1411-
1412- for ( int i = 0 ; i < suppressed . Length ; i += 1 )
1403+
1404+ // Do any error reporting for misused RuleSuppressionIDs here.
1405+ // If the user applied a suppression qualified by a suppression ID,
1406+ // but that suppression didn't apply only because the suppression ID didn't match
1407+ // we let the user know in the form of an error.
1408+ string analyzedFile = diagnostics [ 0 ] ? . Extent ? . File ;
1409+ bool appliedToFile = ! string . IsNullOrEmpty ( analyzedFile ) ;
1410+ string analyzedFileName = appliedToFile ? Path . GetFileName ( analyzedFile ) : null ;
1411+ foreach ( RuleSuppression unappliedSuppression in unappliedSuppressions )
14131412 {
1414- if ( ! suppressed [ i ] )
1413+ string suppressionIDError = string . Format ( Strings . RuleSuppressionIDError , unappliedSuppression . RuleSuppressionID ) ;
1414+
1415+ if ( appliedToFile )
14151416 {
1416- unSuppressedRecords . Add ( diagnostics [ i ] ) ;
1417+ unappliedSuppression . Error = string . Format (
1418+ CultureInfo . CurrentCulture ,
1419+ Strings . RuleSuppressionErrorFormat ,
1420+ unappliedSuppression . StartAttributeLine ,
1421+ analyzedFileName ,
1422+ suppressionIDError ) ;
14171423 }
1424+ else
1425+ {
1426+ unappliedSuppression . Error = string . Format (
1427+ CultureInfo . CurrentCulture ,
1428+ Strings . RuleSuppressionErrorFormatScriptDefinition ,
1429+ unappliedSuppression . StartAttributeLine ,
1430+ suppressionIDError ) ;
1431+ }
1432+
1433+ errors . Add (
1434+ new ErrorRecord (
1435+ new ArgumentException ( unappliedSuppression . Error ) ,
1436+ unappliedSuppression . Error ,
1437+ ErrorCategory . InvalidArgument ,
1438+ unappliedSuppression ) ) ;
14181439 }
14191440
14201441 return result ;
14211442 }
14221443
1423- private Tuple < int , int > [ ] GetOffsetArray ( List < DiagnosticRecord > diagnostics )
1444+ private Tuple < int , int > GetOffset ( DiagnosticRecord diagnostic )
14241445 {
1425- Func < int , int , Tuple < int , int > > GetTuple = ( x , y ) => new Tuple < int , int > ( x , y ) ;
1426- Func < Tuple < int , int > > GetDefaultTuple = ( ) => GetTuple ( 0 , 0 ) ;
1427- var offsets = new Tuple < int , int > [ diagnostics . Count ] ;
1428- for ( int k = 0 ; k < diagnostics . Count ; k ++ )
1446+ IScriptExtent extent = diagnostic . Extent ;
1447+
1448+ if ( extent is null )
14291449 {
1430- var ext = diagnostics [ k ] . Extent ;
1431- if ( ext == null )
1432- {
1433- continue ;
1434- }
1435- if ( ext . StartOffset == 0 && ext . EndOffset == 0 )
1450+ return null ;
1451+ }
1452+
1453+ if ( extent . StartOffset != 0 && extent . EndOffset != 0 )
1454+ {
1455+ return Tuple . Create ( extent . StartOffset , extent . EndOffset ) ;
1456+ }
1457+
1458+ // We're forced to determine the offset ourselves from the lines and columns now
1459+ // Rather than counting every line in the file, we scan through the tokens looking for ones matching the offset
1460+
1461+ Token startToken = null ;
1462+ int i = 0 ;
1463+ for ( ; i < Tokens . Length ; i ++ )
1464+ {
1465+ Token curr = Tokens [ i ] ;
1466+ if ( curr . Extent . StartLineNumber == extent . StartLineNumber
1467+ && curr . Extent . StartColumnNumber == extent . StartColumnNumber )
14361468 {
1437- // check if line and column number correspond to 0 offsets
1438- if ( ext . StartLineNumber == 1
1439- && ext . StartColumnNumber == 1
1440- && ext . EndLineNumber == 1
1441- && ext . EndColumnNumber == 1 )
1442- {
1443- offsets [ k ] = GetDefaultTuple ( ) ;
1444- continue ;
1445- }
1446- // created using the ScriptExtent constructor, which sets
1447- // StartOffset and EndOffset to 0
1448- // find the token the corresponding start line and column number
1449- var startToken = Tokens . Where ( x
1450- => x . Extent . StartLineNumber == ext . StartLineNumber
1451- && x . Extent . StartColumnNumber == ext . StartColumnNumber )
1452- . FirstOrDefault ( ) ;
1453- if ( startToken == null )
1454- {
1455- offsets [ k ] = GetDefaultTuple ( ) ;
1456- continue ;
1457- }
1458- var endToken = Tokens . Where ( x
1459- => x . Extent . EndLineNumber == ext . EndLineNumber
1460- && x . Extent . EndColumnNumber == ext . EndColumnNumber )
1461- . FirstOrDefault ( ) ;
1462- if ( endToken == null )
1463- {
1464- offsets [ k ] = GetDefaultTuple ( ) ;
1465- continue ;
1466- }
1467- offsets [ k ] = GetTuple ( startToken . Extent . StartOffset , endToken . Extent . EndOffset ) ;
1469+ startToken = curr ;
1470+ break ;
14681471 }
1469- else
1472+ }
1473+
1474+ if ( startToken is null )
1475+ {
1476+ return Tuple . Create ( 0 , 0 ) ;
1477+ }
1478+
1479+ Token endToken = null ;
1480+ for ( ; i < Tokens . Length ; i ++ )
1481+ {
1482+ Token curr = Tokens [ i ] ;
1483+ if ( curr . Extent . EndLineNumber == extent . EndLineNumber
1484+ && curr . Extent . EndColumnNumber == extent . EndColumnNumber )
14701485 {
1471- // Extent has valid offsets
1472- offsets [ k ] = GetTuple ( ext . StartOffset , ext . EndOffset ) ;
1486+ endToken = curr ;
1487+ break ;
14731488 }
14741489 }
1475- return offsets ;
1490+
1491+ if ( endToken is null )
1492+ {
1493+ return Tuple . Create ( 0 , 0 ) ;
1494+ }
1495+
1496+ return Tuple . Create ( startToken . Extent . StartOffset , endToken . Extent . EndOffset ) ;
14761497 }
14771498
14781499 public static string [ ] ProcessCustomRulePaths ( string [ ] rulePaths , SessionState sessionState , bool recurse = false )
0 commit comments