From e96a13a71287c50a3e0eed4b9c4aa2b139900a12 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Mon, 29 Jan 2024 01:08:34 +0100 Subject: [PATCH] Add analyzer 'Use raw string literal' (#1375) --- ChangeLog.md | 4 + .../RawStringLiteralCodeFixProvider.cs | 225 +++++++++++++++ ...ecessaryRawStringLiteralCodeFixProvider.cs | 100 ------- src/Analyzers.xml | 23 ++ .../Analysis/UseRawStringLiteralAnalyzer.cs | 93 ++++++ .../CSharp/DiagnosticIdentifiers.Generated.cs | 1 + .../CSharp/DiagnosticRules.Generated.cs | 12 + ...RCS1262UnnecessaryRawStringLiteralTests.cs | 2 +- .../RCS1266UseRawStringLiteralTests.cs | 270 ++++++++++++++++++ .../src/configurationFiles.generated.ts | 6 + 10 files changed, 635 insertions(+), 101 deletions(-) create mode 100644 src/Analyzers.CodeFixes/CSharp/CodeFixes/RawStringLiteralCodeFixProvider.cs delete mode 100644 src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryRawStringLiteralCodeFixProvider.cs create mode 100644 src/Analyzers/CSharp/Analysis/UseRawStringLiteralAnalyzer.cs create mode 100644 src/Tests/Analyzers.Tests/RCS1266UseRawStringLiteralTests.cs diff --git a/ChangeLog.md b/ChangeLog.md index b0c0db2b02..7dad6d0606 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add analyzer "Use raw string literal" [RCS1266](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1266) ([PR](https://github.com/dotnet/roslynator/pull/1375)) + ## [4.10.0] - 2024-01-24 ### Added diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/RawStringLiteralCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/RawStringLiteralCodeFixProvider.cs new file mode 100644 index 0000000000..8c6f2f3974 --- /dev/null +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/RawStringLiteralCodeFixProvider.cs @@ -0,0 +1,225 @@ +#if ROSLYN_4_2 +// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslynator.CodeFixes; + +namespace Roslynator.CSharp.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RawStringLiteralCodeFixProvider))] +[Shared] +public sealed class RawStringLiteralCodeFixProvider : BaseCodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds + { + get + { + return ImmutableArray.Create( + DiagnosticIdentifiers.UnnecessaryRawStringLiteral, + DiagnosticIdentifiers.UseRawStringLiteral); + } + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); + + if (!TryFindFirstAncestorOrSelf(root, context.Span, out SyntaxNode node, predicate: f => f.IsKind(SyntaxKind.StringLiteralExpression, SyntaxKind.InterpolatedStringExpression))) + return; + + Diagnostic diagnostic = context.Diagnostics[0]; + Document document = context.Document; + + if (node is LiteralExpressionSyntax literalExpression) + { + if (literalExpression.Token.IsKind(SyntaxKind.StringLiteralToken)) + { + CodeAction codeAction = CodeAction.Create( + "Use raw string literal", + ct => UseRawStringLiteralAsync(document, literalExpression, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Unnecessary raw string literal", + ct => RefactorAsync(document, literalExpression, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } + else if (node is InterpolatedStringExpressionSyntax interpolatedString) + { + if (interpolatedString.StringStartToken.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken)) + { + CodeAction codeAction = CodeAction.Create( + "Use raw string literal", + ct => UseRawStringLiteralAsync(document, interpolatedString, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Unnecessary raw string literal", + ct => RefactorAsync(document, interpolatedString, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } + } + + private static Task RefactorAsync( + Document document, + LiteralExpressionSyntax literalExpression, + CancellationToken cancellationToken) + { + RawStringLiteralInfo info = RawStringLiteralInfo.Create(literalExpression); + + string newText = info.Text.Substring(info.QuoteCount - 1, info.Text.Length - ((info.QuoteCount * 2) - 2)); + + return document.WithTextChangeAsync(literalExpression.Span, newText, cancellationToken); + } + + private static Task RefactorAsync( + Document document, + InterpolatedStringExpressionSyntax interpolatedString, + CancellationToken cancellationToken) + { + InterpolatedStringExpressionSyntax newInterpolatedString = interpolatedString.ReplaceTokens( + interpolatedString + .Contents + .OfType() + .SelectMany(interpolation => new SyntaxToken[] { interpolation.OpenBraceToken, interpolation.CloseBraceToken }), + (token, _) => + { + if (token.IsKind(SyntaxKind.OpenBraceToken)) + { + return SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithTriviaFrom(token); + } + else + { + return SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithTriviaFrom(token); + } + }); + + string text = newInterpolatedString.ToString(); + int startIndex = newInterpolatedString.StringStartToken.Text.Length; + string newText = "$\"" + text.Substring(startIndex, text.Length - startIndex - newInterpolatedString.StringEndToken.Text.Length) + "\""; + + return document.WithTextChangeAsync(interpolatedString.Span, newText, cancellationToken); + } + + private static Task UseRawStringLiteralAsync( + Document document, + LiteralExpressionSyntax literalExpression, + CancellationToken cancellationToken) + { + string text = literalExpression.Token.Text; + int quoteCount = 0; + Match match = Regex.Match(text, @"""+"); + + while (match.Success) + { + if (match.Length > quoteCount) + quoteCount = match.Length / 2; + + match = match.NextMatch(); + } + + quoteCount = (quoteCount > 2) ? quoteCount + 1 : 3; + var quotes = new string('"', quoteCount); + + var sb = new StringBuilder(); + sb.AppendLine(quotes); + sb.Append(text, 2, text.Length - 3); + sb.Replace("\"\"", "\"", quoteCount, sb.Length - quoteCount); + sb.AppendLine(); + sb.Append(quotes); + + return document.WithTextChangeAsync(literalExpression.Span, sb.ToString(), cancellationToken); + } + + private static Task UseRawStringLiteralAsync( + Document document, + InterpolatedStringExpressionSyntax interpolatedString, + CancellationToken cancellationToken) + { + int quoteCount = 0; + int braceCount = 0; + + foreach (InterpolatedStringContentSyntax content in interpolatedString.Contents) + { + if (content is InterpolatedStringTextSyntax interpolatedText) + { + Match match = Regex.Match(interpolatedText.TextToken.Text, @"""+"); + + while (match.Success) + { + if (match.Length > quoteCount) + quoteCount = match.Length / 2; + + match = match.NextMatch(); + } + + match = Regex.Match(interpolatedText.TextToken.Text, @"\{+"); + + while (match.Success) + { + if (match.Length > braceCount) + braceCount = match.Length / 2; + + match = match.NextMatch(); + } + } + } + + quoteCount = (quoteCount > 2) ? quoteCount + 1 : 3; + var quotes = new string('"', quoteCount); + + var sb = new StringBuilder(); + sb.Append('$', braceCount + 1); + sb.AppendLine(quotes); + + foreach (InterpolatedStringContentSyntax content in interpolatedString.Contents) + { + if (content is InterpolatedStringTextSyntax interpolatedText) + { + string text = interpolatedText.TextToken.Text; + int startIndex = sb.Length; + sb.Append(text); + sb.Replace("\"\"", "\"", startIndex, sb.Length - startIndex); + sb.Replace("{{", "{", startIndex, sb.Length - startIndex); + sb.Replace("}}", "}", startIndex, sb.Length - startIndex); + } + else + { + sb.Append('{', braceCount); + sb.Append(content.ToString()); + sb.Append('}', braceCount); + } + } + + sb.AppendLine(); + sb.Append(quotes); + + return document.WithTextChangeAsync(interpolatedString.Span, sb.ToString(), cancellationToken); + } +} +#endif diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryRawStringLiteralCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryRawStringLiteralCodeFixProvider.cs deleted file mode 100644 index e727358c72..0000000000 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryRawStringLiteralCodeFixProvider.cs +++ /dev/null @@ -1,100 +0,0 @@ -#if ROSLYN_4_2 -// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Roslynator.CodeFixes; - -namespace Roslynator.CSharp.CodeFixes; - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnnecessaryRawStringLiteralCodeFixProvider))] -[Shared] -public sealed class UnnecessaryRawStringLiteralCodeFixProvider : BaseCodeFixProvider -{ - private const string Title = "Unnecessary raw string literal"; - - public override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(DiagnosticIdentifiers.UnnecessaryRawStringLiteral); } - } - - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); - - if (!TryFindFirstAncestorOrSelf(root, context.Span, out SyntaxNode node, predicate: f => f.IsKind(SyntaxKind.StringLiteralExpression, SyntaxKind.InterpolatedStringExpression))) - return; - - Diagnostic diagnostic = context.Diagnostics[0]; - Document document = context.Document; - - if (node is LiteralExpressionSyntax literalExpression) - { - CodeAction codeAction = CodeAction.Create( - Title, - ct => RefactorAsync(document, literalExpression, ct), - GetEquivalenceKey(diagnostic)); - - context.RegisterCodeFix(codeAction, diagnostic); - } - else if (node is InterpolatedStringExpressionSyntax interpolatedString) - { - CodeAction codeAction = CodeAction.Create( - Title, - ct => RefactorAsync(document, interpolatedString, ct), - GetEquivalenceKey(diagnostic)); - - context.RegisterCodeFix(codeAction, diagnostic); - } - } - - private static Task RefactorAsync( - Document document, - LiteralExpressionSyntax literalExpression, - CancellationToken cancellationToken) - { - RawStringLiteralInfo info = RawStringLiteralInfo.Create(literalExpression); - - string newText = info.Text.Substring(info.QuoteCount - 1, info.Text.Length - ((info.QuoteCount * 2) - 2)); - - return document.WithTextChangeAsync(literalExpression.Span, newText, cancellationToken); - } - - private static Task RefactorAsync( - Document document, - InterpolatedStringExpressionSyntax interpolatedString, - CancellationToken cancellationToken) - { - InterpolatedStringExpressionSyntax newInterpolatedString = interpolatedString.ReplaceTokens( - interpolatedString - .Contents - .OfType() - .SelectMany(interpolation => new SyntaxToken[] { interpolation.OpenBraceToken, interpolation.CloseBraceToken }), - (token, _) => - { - if (token.IsKind(SyntaxKind.OpenBraceToken)) - { - return SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithTriviaFrom(token); - } - else - { - return SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithTriviaFrom(token); - } - }); - - string text = newInterpolatedString.ToString(); - int startIndex = newInterpolatedString.StringStartToken.Text.Length; - string newText = "$\"" + text.Substring(startIndex, text.Length - startIndex - newInterpolatedString.StringEndToken.Text.Length) + "\""; - - return document.WithTextChangeAsync(interpolatedString.Span, newText, cancellationToken); - } -} -#endif diff --git a/src/Analyzers.xml b/src/Analyzers.xml index 1a2617c781..8c6ee82832 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -7687,6 +7687,29 @@ finally { DoSomethingElse(); } +]]> + + + + + RCS1266 + UseRawStringLiteral + Use raw string literal + Info + true + + + + diff --git a/src/Analyzers/CSharp/Analysis/UseRawStringLiteralAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseRawStringLiteralAnalyzer.cs new file mode 100644 index 0000000000..2f8c5e46c6 --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/UseRawStringLiteralAnalyzer.cs @@ -0,0 +1,93 @@ +#if ROSLYN_4_2 +// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Roslynator.CSharp.Analysis; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UseRawStringLiteralAnalyzer : BaseDiagnosticAnalyzer +{ + private static ImmutableArray _supportedDiagnostics; + + public override ImmutableArray SupportedDiagnostics + { + get + { + if (_supportedDiagnostics.IsDefault) + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseRawStringLiteral); + + return _supportedDiagnostics; + } + } + + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); + + context.RegisterCompilationStartAction(startContext => + { + if (((CSharpCompilation)startContext.Compilation).LanguageVersion >= LanguageVersion.CSharp11) + { + startContext.RegisterSyntaxNodeAction(f => AnalyzeStringLiteralExpression(f), SyntaxKind.StringLiteralExpression); + startContext.RegisterSyntaxNodeAction(f => AnalyzeInterpolatedStringExpression(f), SyntaxKind.InterpolatedStringExpression); + } + }); + } + + private static void AnalyzeStringLiteralExpression(SyntaxNodeAnalysisContext context) + { + var node = (LiteralExpressionSyntax)context.Node; + + SyntaxToken token = node.Token; + + if (!token.IsKind(SyntaxKind.StringLiteralToken)) + return; + + string s = token.Text; + + if (s.StartsWith("@") + && s.IndexOf("\n", 2, s.Length - 3) >= 0 + && s.IndexOf("\"", 2, s.Length - 3) >= 0) + { + DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UseRawStringLiteral, Location.Create(node.SyntaxTree, new TextSpan(node.SpanStart, 2))); + } + } + + private static void AnalyzeInterpolatedStringExpression(SyntaxNodeAnalysisContext context) + { + var interpolatedString = (InterpolatedStringExpressionSyntax)context.Node; + + if (!interpolatedString.StringStartToken.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken)) + return; + + var containsNewLine = false; + var containsQuote = false; + + foreach (InterpolatedStringContentSyntax content in interpolatedString.Contents) + { + if (content is InterpolatedStringTextSyntax interpolatedText) + { + string text = interpolatedText.TextToken.Text; + + if (!containsNewLine) + containsNewLine = text.Contains("\n"); + + if (!containsQuote) + containsQuote = text.Contains("\""); + + if (containsNewLine && containsQuote) + { + DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UseRawStringLiteral, interpolatedString.StringStartToken); + break; + } + } + } + } +} +#endif diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index ead38d0d43..7b2d3f839d 100644 --- a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs @@ -222,5 +222,6 @@ public static partial class DiagnosticIdentifiers public const string InvalidReferenceInDocumentationComment = "RCS1263"; public const string UseVarOrExplicitType = "RCS1264"; public const string RemoveRedundantCatchBlock = "RCS1265"; + public const string UseRawStringLiteral = "RCS1266"; } } \ No newline at end of file diff --git a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs index ce5ecaa8c8..5556773a6f 100644 --- a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs @@ -2627,5 +2627,17 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.RemoveRedundantCatchBlock, customTags: Array.Empty()); + /// RCS1266 + public static readonly DiagnosticDescriptor UseRawStringLiteral = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.UseRawStringLiteral, + title: "Use raw string literal", + messageFormat: "Use raw string literal", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: DiagnosticIdentifiers.UseRawStringLiteral, + customTags: Array.Empty()); + } } \ No newline at end of file diff --git a/src/Tests/Analyzers.Tests/RCS1262UnnecessaryRawStringLiteralTests.cs b/src/Tests/Analyzers.Tests/RCS1262UnnecessaryRawStringLiteralTests.cs index b840daeb8b..d1c433f86a 100644 --- a/src/Tests/Analyzers.Tests/RCS1262UnnecessaryRawStringLiteralTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1262UnnecessaryRawStringLiteralTests.cs @@ -9,7 +9,7 @@ namespace Roslynator.CSharp.Analysis.Tests; -public class RCS1262UnnecessaryRawStringLiteralTests : AbstractCSharpDiagnosticVerifier +public class RCS1262UnnecessaryRawStringLiteralTests : AbstractCSharpDiagnosticVerifier { public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UnnecessaryRawStringLiteral; diff --git a/src/Tests/Analyzers.Tests/RCS1266UseRawStringLiteralTests.cs b/src/Tests/Analyzers.Tests/RCS1266UseRawStringLiteralTests.cs new file mode 100644 index 0000000000..6469a878ba --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1266UseRawStringLiteralTests.cs @@ -0,0 +1,270 @@ +#if ROSLYN_4_2 +// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Roslynator.CSharp.CodeFixes; +using Roslynator.Testing.CSharp; +using Xunit; + +namespace Roslynator.CSharp.Analysis.Tests; + +public class RCS1266UseRawStringLiteralTests : AbstractCSharpDiagnosticVerifier +{ + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseRawStringLiteral; + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_StringLiteral() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + string s = [|@"|] + ""foo"" +"; + } +} +""", """" +class C +{ + void M() + { + string s = """ + + "foo" + +"""; + } +} +""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_InterpolatedString_DollarFirst() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + string s = [|$@"|] +"" {""} "" {"foo"} +"; + } +} +""", """" +class C +{ + void M() + { + string s = $""" + +" {""} " {"foo"} + +"""; + } +} +""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_InterpolatedString_AtFirst() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + string s = [|@$"|] +"" {""} "" {"foo"} +"; + } +} +""", """" +class C +{ + void M() + { + string s = $""" + +" {""} " {"foo"} + +"""; + } +} +""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_InterpolatedString_MoreQuotes() + { + await VerifyDiagnosticAndFixAsync(""""""""" +class C +{ + void M() + { + string s = [|@$"|] +"""""""" {""} "" {"foo"} +"; + } +} +""""""""", """""" +class C +{ + void M() + { + string s = $""""" + +"""" {""} " {"foo"} + +"""""; + } +} +""""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_InterpolatedString_ContainsBrace() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + string s = [|@$"|] +"" {string.Empty} {{ }} +"; + } +} +""", """" +class C +{ + void M() + { + string s = $$""" + +" {{string.Empty}} { } + +"""; + } +} +""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_InterpolatedString_ContainsBraces() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + string s = [|@$"|] +"" {string.Empty} {{{{ }}}} +"; + } +} +""", """" +class C +{ + void M() + { + string s = $$$""" + +" {{{string.Empty}}} {{ }} + +"""; + } +} +""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_LiteralExpression2() + { + await VerifyDiagnosticAndFixAsync("""" +class C +{ + void M() + { + string s = [|@"|]"" +"""; + } +} +"""", """" +class C +{ + void M() + { + string s = """ +" +" +"""; + } +} +""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task Test_StringLiteral_MoreQuotes() + { + await VerifyDiagnosticAndFixAsync(""""""" +class C +{ + void M() + { + string s = [|@"|] + """"""foo"" +"; + } +} +""""""", """""" +class C +{ + void M() + { + string s = """" + + """foo" + +""""; + } +} +""""""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task TestNoDiagnostic_StringLiteral_NoQuotes() + { + await VerifyNoDiagnosticAsync(""" +class C +{ + void M() + { + string s = @" + foo +"; + } +} +"""); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseRawStringLiteral)] + public async Task TestNoDiagnostic_InterpolatedString_NoQuotes() + { + await VerifyNoDiagnosticAsync(""" +class C +{ + void M() + { + string s = $@" + {""} {"foo"} +"; + } +} +"""); + } +} +#endif diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts index e52c21533c..95891436f0 100644 --- a/src/VisualStudioCode/package/src/configurationFiles.generated.ts +++ b/src/VisualStudioCode/package/src/configurationFiles.generated.ts @@ -907,6 +907,12 @@ roslynator_analyzers.enabled_by_default = true|false #dotnet_diagnostic.rcs1264.severity = none # Options: roslynator_use_var +# Remove redundant catch block +#dotnet_diagnostic.rcs1265.severity = suggestion + +# Use raw string literal +#dotnet_diagnostic.rcs1266.severity = suggestion + # Use pattern matching #dotnet_diagnostic.rcs9001.severity = silent