Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement S6326 for both C# and VB.NET #7345

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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;
}
Expand Up @@ -22,18 +22,19 @@

namespace SonarAnalyzer.RegularExpressions;

internal sealed class RegexContext
[DebuggerDisplay("Pattern = {Pattern}, Options = {Options}")]
public sealed class RegexContext
{
private static readonly RegexOptions ValidationMask = (RegexOptions)int.MaxValue ^ RegexOptions.Compiled;

private static readonly string[] MatchMethods = new[]
{
private static readonly string[] MatchMethods =
[
nameof(Regex.IsMatch),
nameof(Regex.Match),
nameof(Regex.Matches),
nameof(Regex.Replace),
nameof(Regex.Split),
};
];

public SyntaxNode PatternNode { get; }
public string Pattern { get; }
Expand Down Expand Up @@ -102,9 +103,14 @@ 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);
FindRegexOptions(language, model, options));
}

private static RegexOptions? FindRegexOptions<TSyntaxKind>(ILanguageFacade<TSyntaxKind> language, SemanticModel model, SyntaxNode options) where TSyntaxKind : struct =>
language.FindConstantValue(model, options) is int constant
? (RegexOptions)constant
: null;

private static SyntaxNode TryGetNonParamsSyntax(IMethodSymbol method, IMethodParameterLookup parameters, string paramName) =>
method.Parameters.SingleOrDefault(x => x.Name == paramName) is { } param
&& parameters.TryGetNonParamsSyntax(param, out var node)
Expand Down
@@ -0,0 +1,49 @@
/*
* 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 SonarAnalyzer.RegularExpressions;

namespace SonarAnalyzer.Rules;

public abstract class RegexAnalyzerBase<TSyntaxKind> : SonarDiagnosticAnalyzer<TSyntaxKind>
where TSyntaxKind : struct
{
protected RegexAnalyzerBase(string diagnosticId) : base(diagnosticId) { }

protected abstract void Analyze(SonarSyntaxNodeReportingContext context, RegexContext regexContext);

protected sealed 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);
}
}
Expand Up @@ -22,7 +22,7 @@

namespace SonarAnalyzer.Rules;

public abstract class RegexMustHaveValidSyntaxBase<TSyntaxKind> : SonarDiagnosticAnalyzer<TSyntaxKind>
public abstract class RegexMustHaveValidSyntaxBase<TSyntaxKind> : RegexAnalyzerBase<TSyntaxKind>
where TSyntaxKind : struct
{
private const string DiagnosticId = "S5856";
Expand All @@ -31,29 +31,11 @@ public abstract class RegexMustHaveValidSyntaxBase<TSyntaxKind> : SonarDiagnosti

protected RegexMustHaveValidSyntaxBase() : base(DiagnosticId) { }

protected override void Initialize(SonarAnalysisContext context)
protected sealed override void Analyze(SonarSyntaxNodeReportingContext context, RegexContext regexContext)
{
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?.ParseError is { } error)
if (regexContext?.ParseError is { } error)
{
c.ReportIssue(Diagnostic.Create(Rule, context.PatternNode.GetLocation(), error.Message));
context.ReportIssue(Diagnostic.Create(Rule, regexContext.PatternNode.GetLocation(), error.Message));
}
}
}
@@ -0,0 +1,48 @@
/*
* 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> : RegexAnalyzerBase<TSyntaxKind>
where TSyntaxKind : struct
{
private const string DiagnosticId = "S101";

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

protected RegexShouldNotContainMultipleSpacesBase() : base(DiagnosticId) { }

protected sealed override void Analyze(SonarSyntaxNodeReportingContext context, RegexContext regexContext)
{
if (regexContext?.Regex is not null
&& !IgnoresPatternWhitespace(regexContext)
&& regexContext.Pattern.Contains(" "))
{
context.ReportIssue(Diagnostic.Create(Rule, regexContext.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-2024 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.Test.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();
}