From 7c64d8ac5944c4db4b1b788eaea49ba173899f81 Mon Sep 17 00:00:00 2001 From: Corniel Nobel Date: Wed, 7 Jun 2023 08:49:49 +0200 Subject: [PATCH] Implement S6326 for both C# and VB.NET. --- .../RegexShouldNotContainMultipleSpaces.cs | 27 ++++ .../Facade/ILanguageFacade.cs | 20 +++ .../RegularExpressions/RegexContext.cs | 3 +- ...RegexShouldNotContainMultipleSpacesBase.cs | 66 ++++++++++ .../RegexShouldNotContainMultipleSpaces.cs | 27 ++++ .../RegexMustHaveValidSyntaxTest.cs | 2 - ...RegexShouldNotContainMultipleSpacesTest.cs | 46 +++++++ .../RegexShouldNotContainMultipleSpaces.cs | 124 ++++++++++++++++++ .../RegexShouldNotContainMultipleSpaces.vb | 34 +++++ 9 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 analyzers/src/SonarAnalyzer.CSharp/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs create mode 100644 analyzers/src/SonarAnalyzer.Common/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesBase.cs create mode 100644 analyzers/src/SonarAnalyzer.VisualBasic/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesTest.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.vb diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs new file mode 100644 index 00000000000..472b2a317d8 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs @@ -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 +{ + protected override ILanguageFacade Language => CSharpFacade.Instance; +} diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/ILanguageFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/ILanguageFacade.cs index 30b09520334..607a63c196d 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/ILanguageFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/ILanguageFacade.cs @@ -44,3 +44,23 @@ public interface ILanguageFacade : ILanguageFacade ISyntaxKindFacade SyntaxKind { get; } ITrackerFacade Tracker { get; } } + +public static class LanguageFacadeExtensions +{ + public static TEnum? FindConstantEnum(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; + } + } +} diff --git a/analyzers/src/SonarAnalyzer.Common/RegularExpressions/RegexContext.cs b/analyzers/src/SonarAnalyzer.Common/RegularExpressions/RegexContext.cs index 15b587b133f..ce1ffd7e5cc 100644 --- a/analyzers/src/SonarAnalyzer.Common/RegularExpressions/RegexContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/RegularExpressions/RegexContext.cs @@ -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; @@ -102,7 +103,7 @@ private static RegexContext FromMethod(ILanguageFacade pattern, language.FindConstantValue(model, pattern) as string, options, - language.FindConstantValue(model, options) is RegexOptions value ? value : null); + language.FindConstantEnum(model, options)); } private static SyntaxNode TryGetNonParamsSyntax(IMethodSymbol method, IMethodParameterLookup parameters, string paramName) => diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesBase.cs new file mode 100644 index 00000000000..6e81bc77f60 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesBase.cs @@ -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 : SonarDiagnosticAnalyzer + 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); +} diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs new file mode 100644 index 00000000000..82fafb23c78 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs @@ -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 +{ + protected override ILanguageFacade Language => VisualBasicFacade.Instance; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexMustHaveValidSyntaxTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexMustHaveValidSyntaxTest.cs index 72a2e08a117..56499a1d266 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexMustHaveValidSyntaxTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexMustHaveValidSyntaxTest.cs @@ -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; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesTest.cs new file mode 100644 index 00000000000..f8377d0d7a7 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/RegularExpressions/RegexShouldNotContainMultipleSpacesTest.cs @@ -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() + .WithBasePath("RegularExpressions") + .AddReferences(MetadataReferenceFacade.RegularExpressions) + .AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations()); + + private readonly VerifierBuilder builderVB = new VerifierBuilder() + .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(); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs new file mode 100644 index 00000000000..3cbfbe2ca5a --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.cs @@ -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; } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.vb new file mode 100644 index 00000000000..130521f5057 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/RegularExpressions/RegexShouldNotContainMultipleSpaces.vb @@ -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 + + + 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 + + ' Noncompliant + Public Property Attribute As String +End Class