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 8 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions analyzers/NuGet.Config
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,24 @@
<trustedSigners>
<clear />
<repository name="nuget.org" serviceIndex="https://api.nuget.org/v3/index.json">
<!-- Subject Name: CN=NuGet.org Repository by Microsoft, valid from 2018-04-10 -->
<!-- Subject Name: CN=NuGet.org Repository by Microsoft, valid from 10/04/2018 -->
<certificate fingerprint="0E5F38F57DC1BCC806D8494F4F90FBCEDD988B46760709CBEEC6F4219AA6157D" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
<!-- Subject Name: CN=NuGet.org Repository by Microsoft, valid from 2021-02-16 -->
<!-- Subject Name: CN=NuGet.org Repository by Microsoft, valid from 16/02/2021 -->
<certificate fingerprint="5A2901D6ADA3D18260B9C6DFE2133C95D74B9EEF6AE0E5DC334C8454D1477DF4" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
<!-- Subject Name: CN=NuGet.org Repository by Microsoft, valid from 2024-02-23 -->
<certificate fingerprint="1F4B311D9ACC115C8DC8018B5A49E00FCE6DA8E2855F9F014CA6F34570BC482D" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
<!-- sharwell = author of StyleCop.Analyzers -->
<!-- test dependencies: -->
<!-- meirb = Meir Blachman, author of FluentAssertions.Analyzers -->
<!-- kzu = Daniel Cazzulino, author of Moq -->
<!-- jonorossi = Jonathon Rossi, maintainer of Castle Project -->
<!-- onovotny = Claire Novotny, author of Humanizer.Core -->
<!-- SteveGilham = author of AltCover-->
<!-- jamesnk = author of Newtonsoft.Json -->
<!-- commandlineparser = author of CommandLineParser -->
<!-- grpc-packages = author of Grpc.Tools -->
<!-- Nsubstitute = author of NSubstitute -->
<owners>protobuf-packages;Microsoft;sharwell;meirb;dotnetfoundation;castleproject;jonorossi;onovotny;fluentassertions;SteveGilham;jamesnk;commandlineparser;grpc-packages;Fody;NSubstitute</owners>
<owners>protobuf-packages;Microsoft;sharwell;meirb;kzu;dotnetfoundation;castleproject;jonorossi;onovotny;fluentassertions;SteveGilham;jamesnk;commandlineparser;grpc-packages</owners>
</repository>
<author name="SonarSource">
<!-- Subject Name: CN=SonarSource SA, valid from 2023-10-17 -->
<certificate fingerprint="A943C46DBA193D99C1135FFE33D3337524E9B3F05B416B9314E168CD206EF427" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
<certificate fingerprint="FC4D3F3F815C1B56A656F1A5D9456AF04B469267D945786057175049B15A62A0" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
</author>
</trustedSigners>
</configuration>
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
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 @@ private static RegexContext FromMethod<TSyntaxKind>(ILanguageFacade<TSyntaxKind>
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
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
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(Rule, context.PatternNode, error.Message);
context.ReportIssue(Rule, regexContext.PatternNode, error.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
Loading