Skip to content

Commit

Permalink
Implement S6326 for both C# and VB.NET.
Browse files Browse the repository at this point in the history
  • Loading branch information
Corniel Nobel committed Jun 7, 2023
1 parent a720d7f commit b1d2709
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -12,7 +12,7 @@ languages in [SonarQube](http://www.sonarqube.org/), [SonarCloud](https://sonarc

## Features

* [400+ C# rules](https://rules.sonarsource.com/csharp) and [180+ VB.​NET rules](https://rules.sonarsource.com/vbnet)
* [400+ C# rules](https://rules.sonarsource.com/csharp) and [190+ VB.​NET rules](https://rules.sonarsource.com/vbnet)
* Metrics (cognitive complexity, duplications, number of lines etc.)
* Import of [test coverage reports](https://community.sonarsource.com/t/9871) from Visual Studio Code Coverage, dotCover, OpenCover, Coverlet, Altcover.
* Import of third party Roslyn Analyzers results
Expand Down
@@ -0,0 +1,27 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Rules.CSharp;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class RegexShouldNotContainMultipleSpaces : RegexShouldNotContainMultipleSpacesBase<SyntaxKind>
{
protected override ILanguageFacade<SyntaxKind> Language => CSharpFacade.Instance;
}
20 changes: 20 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Facade/ILanguageFacade.cs
Expand Up @@ -44,3 +44,23 @@ public interface ILanguageFacade<TSyntaxKind> : ILanguageFacade
ISyntaxKindFacade<TSyntaxKind> SyntaxKind { get; }
ITrackerFacade<TSyntaxKind> Tracker { get; }
}

public static class LanguageFacadeExtensions
{
public static TEnum? FindConstantEnum<TEnum>(this ILanguageFacade facade, SemanticModel model, SyntaxNode node) where TEnum : struct
{
var value = facade.FindConstantValue(model, node);
if (value is TEnum @enum)
{
return @enum;
}
else if (value is int || value is long)
{
return (TEnum)value;
}
else
{
return null;
}
}
}
Expand Up @@ -22,6 +22,7 @@

namespace SonarAnalyzer.RegularExpressions;

[DebuggerDisplay("Pattern = {Pattern}, Options = {Options}")]
internal sealed class RegexContext
{
private static readonly RegexOptions ValidationMask = (RegexOptions)int.MaxValue ^ RegexOptions.Compiled;
Expand Down Expand Up @@ -102,7 +103,7 @@ public RegexContext(SyntaxNode patternNode, string pattern, SyntaxNode optionsNo
pattern,
language.FindConstantValue(model, pattern) as string,
options,
language.FindConstantValue(model, options) is RegexOptions value ? value : null);
language.FindConstantEnum<RegexOptions>(model, options));
}

private static SyntaxNode TryGetNonParamsSyntax(IMethodSymbol method, IMethodParameterLookup parameters, string paramName) =>
Expand Down
@@ -0,0 +1,66 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Text.RegularExpressions;
using SonarAnalyzer.RegularExpressions;

namespace SonarAnalyzer.Rules;

public abstract class RegexShouldNotContainMultipleSpacesBase<TSyntaxKind> : SonarDiagnosticAnalyzer<TSyntaxKind>
where TSyntaxKind : struct
{
private const string DiagnosticId = "S6326";

protected sealed override string MessageFormat => "Regular expressions should not contain multiple spaces.";

protected RegexShouldNotContainMultipleSpacesBase() : base(DiagnosticId) { }

protected override void Initialize(SonarAnalysisContext context)
{
context.RegisterNodeAction(
Language.GeneratedCodeRecognizer,
c => Analyze(c, RegexContext.FromCtor(Language, c.SemanticModel, c.Node)),
Language.SyntaxKind.ObjectCreationExpressions);

context.RegisterNodeAction(
Language.GeneratedCodeRecognizer,
c => Analyze(c, RegexContext.FromMethod(Language, c.SemanticModel, c.Node)),
Language.SyntaxKind.InvocationExpression);

context.RegisterNodeAction(
Language.GeneratedCodeRecognizer,
c => Analyze(c, RegexContext.FromAttribute(Language, c.SemanticModel, c.Node)),
Language.SyntaxKind.Attribute);
}

private void Analyze(SonarSyntaxNodeReportingContext c, RegexContext context)
{
if (context?.Regex is { }
&& !IgnoresPatternWhitespace(context)
&& context.Pattern.Contains(" "))
{
c.ReportIssue(Diagnostic.Create(Rule, context.PatternNode.GetLocation()));
}
}

private bool IgnoresPatternWhitespace(RegexContext context) =>
context.Options is { } options
&& options.HasFlag(RegexOptions.IgnorePatternWhitespace);
}
@@ -0,0 +1,27 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Rules.VisualBasic;

[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
public sealed class RegexShouldNotContainMultipleSpaces : RegexShouldNotContainMultipleSpacesBase<SyntaxKind>
{
protected override ILanguageFacade<SyntaxKind> Language => VisualBasicFacade.Instance;
}
Expand Up @@ -18,8 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Text.RegularExpressions;
using SonarAnalyzer.RegularExpressions;
using CS = SonarAnalyzer.Rules.CSharp;
using VB = SonarAnalyzer.Rules.VisualBasic;

Expand Down
@@ -0,0 +1,46 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using CS = SonarAnalyzer.Rules.CSharp;
using VB = SonarAnalyzer.Rules.VisualBasic;

namespace SonarAnalyzer.UnitTest.Rules;

[TestClass]
public class RegexShouldNotContainMultipleSpacesTest
{
private readonly VerifierBuilder builderCS = new VerifierBuilder<CS.RegexShouldNotContainMultipleSpaces>()
.WithBasePath("RegularExpressions")
.AddReferences(MetadataReferenceFacade.RegularExpressions)
.AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations());

private readonly VerifierBuilder builderVB = new VerifierBuilder<VB.RegexShouldNotContainMultipleSpaces>()
.WithBasePath("RegularExpressions")
.AddReferences(MetadataReferenceFacade.RegularExpressions)
.AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations());

[TestMethod]
public void RegexShouldNotContainMultipleSpaces_CS() =>
builderCS.AddPaths("RegexShouldNotContainMultipleSpaces.cs").Verify();

[TestMethod]
public void RegexShouldNotContainMultipleSpaces_VB() =>
builderVB.AddPaths("RegexShouldNotContainMultipleSpaces.vb").Verify();
}
@@ -0,0 +1,124 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

class Compliant
{
void Ctor()
{
var defaultOrder = new Regex("single space"); // Compliant

var namedArgs = new Regex(
pattern: "single space");

var noRegex = new NoRegex("single space"); // Compliant

var singleOption = new Regex("ignore pattern white space", RegexOptions.IgnorePatternWhitespace); // Compliant
var mixedOptions = new Regex("ignore pattern white space", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); // Compliant
}

void Static()
{
var isMatch = Regex.IsMatch("some input", "single space"); // Compliant
var noRegex = NoRegex.IsMatch("some input", "multiple white spaces"); // Compliant
}

void Unknown(string unknown)
{
var regex = new NoRegex(unknown + "multiple white spaces"); // Compliant
}

bool ConcatanationMultiline(string input)
{
return Regex.IsMatch(input, "single space"
+ "|b"
+ "|c"
+ "|d]"); // Compliant
}

bool ConcatanationSingleIne(string input)
{
return Regex.IsMatch(input, "a" + "|b" + "|c" + "|single white space"); // Compliant
}

[RegularExpression("single space")] // Compliant
public string Attribute { get; set; }

bool WhiteSpaceVariations(string input)
{
return Regex.IsMatch(input, " with multple single spaces ")
|| Regex.IsMatch(input, "without_spaces")
|| Regex.IsMatch(input, "with\ttab")
|| Regex.IsMatch(input, "")
|| Regex.IsMatch(input, "ignore pattern white space", RegexOptions.IgnorePatternWhitespace);
}
}

class Noncompliant
{
private const string Prefix = ".*";

void Ctor()
{
var patternOnly = new Regex("multiple white spaces"); // Noncompliant {{Regular expressions should not contain multiple spaces.}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^

var withConst = new Regex(Prefix + "multiple white spaces"); // Noncompliant
}

void Static()
{
var isMatch = Regex.IsMatch("some input", "multiple white spaces"); // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^^^^
var match = Regex.Match("some input", "multiple white spaces"); // Noncompliant
var matches = Regex.Matches("some input", "multiple white spaces"); // Noncompliant
var split = Regex.Split("some input", "multiple white spaces"); // Noncompliant

var replace = Regex.Replace("some input", "multiple white spaces", "some replacement"); // Noncompliant
}

bool Multiline(string input)
{
return Regex.IsMatch(input,
@"|b
|c
|multiple white spaces"); // Noncompliant @-2
}

bool ConcatanationMultiline(string input)
{
return Regex.IsMatch(input, "a" // Noncompliant
+ "|b"
+ "|c"
+ "|multiple white spaces");
}

bool ConcatanationSingleIne(string input)
{
return Regex.IsMatch(input, "a" + "|b" + "|c" + "|multiple white spaces"); // Noncompliant
}

[RegularExpression("multiple white spaces")] // Noncompliant
public string Attribute { get; set; }

[System.ComponentModel.DataAnnotations.RegularExpression("multiple white spaces")] // Noncompliant
public string AttributeFullySpecified { get; set; }

[global::System.ComponentModel.DataAnnotations.RegularExpression("multiple white spaces")] // Noncompliant
public string AttributeGloballySpecified { get; set; }
}

class DoesNotCrash
{
bool UnknownVariable(string input)
{
return Regex.IsMatch(input, "a" + undefined); // Error CS0103 The name 'undefined' does not exist in the current context
}
}

public class NoRegex
{
public NoRegex(string pattern) { }

public static bool IsMatch(string input, string pattern) { return true; }
}
@@ -0,0 +1,34 @@
Imports System.ComponentModel.DataAnnotations
Imports System.Text.RegularExpressions

Class Compliant
Private Sub Ctor()
Dim defaultOrder = New Regex("single space")
Dim namedArgs = New Regex(options:=RegexOptions.IgnorePatternWhitespace, pattern:="ignore pattern white space")
End Sub

Private Sub [Static]()
Dim isMatch = Regex.IsMatch("some input", "single space")
End Sub

<RegularExpression("single space")>
Public Property Attribute As String
End Class

Class Noncompliant
Private Sub Ctor()
Dim patternOnly = New Regex("multiple white spaces") ' Noncompliant {{Regular expressions should not contain multiple spaces.}}
' ^^^^^^^^^^^^^^^^^^^^^^^^^
End Sub

Private Sub [Static]()
Dim isMatch = Regex.IsMatch("some input", "multiple white spaces") ' Noncompliant
Dim match = Regex.Match("some input", "multiple white spaces") ' Noncompliant
Dim matches = Regex.Matches("some input", "multiple white spaces") ' Noncompliant
Dim split = Regex.Split("some input", "multiple white spaces") ' Noncompliant
Dim replace = Regex.Replace("some input", "multiple white spaces", "some replacement") ' Noncompliant
End Sub

<RegularExpression("multiple white spaces")> ' Noncompliant
Public Property Attribute As String
End Class

0 comments on commit b1d2709

Please sign in to comment.