From ef45533af48f1a8f136717e228e6f9ffa007ca28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:56:25 +0000 Subject: [PATCH 1/8] Initial plan From 5bbbec27f69483812624c31301f767380877a1d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:13:27 +0000 Subject: [PATCH 2/8] Add CA2026 analyzer and fixer for JsonDocument.Parse().RootElement pattern Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../AnalyzerReleases.Unshipped.md | 1 + .../MicrosoftNetCoreAnalyzersResources.resx | 12 + .../Runtime/PreferJsonElementParse.Fixer.cs | 98 +++++ .../Runtime/PreferJsonElementParse.cs | 203 +++++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 20 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 20 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 20 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 20 + .../DiagnosticCategoryAndIdRanges.txt | 2 +- .../Runtime/PreferJsonElementParseTests.cs | 403 ++++++++++++++++++ 19 files changed, 978 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs create mode 100644 src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs create mode 100644 src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md index 0ecf6597797e..589e86fc4126 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md @@ -10,3 +10,4 @@ CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.mic CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023) CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024) CA2025 | Reliability | Disabled | DoNotPassDisposablesIntoUnawaitedTasksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2025) +CA2026 | Reliability | Info | PreferJsonElementParse, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 55948fcbc88d..bacbfc1fe9cb 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2054,6 +2054,18 @@ Widening and user defined conversions are not supported with generic types. Use '{0}.{1}' + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + Use 'JsonElement.Parse' + Using concrete types avoids virtual or interface call overhead and enables inlining. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs new file mode 100644 index 000000000000..5873fe3934e6 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// Fixer for . + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class PreferJsonElementParseFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(PreferJsonElementParse.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + SemanticModel model = await doc.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + SyntaxNode node = root.FindNode(context.Span, getInnermostNodeForTie: true); + if (node == null) + { + return; + } + + IOperation? operation = model.GetOperation(node, context.CancellationToken); + if (operation is not IPropertyReferenceOperation propertyReference) + { + return; + } + + // Verify this is the RootElement property access + if (propertyReference.Property.Name != "RootElement") + { + return; + } + + // Verify the instance is an invocation to JsonDocument.Parse + if (propertyReference.Instance is not IInvocationOperation invocation) + { + return; + } + + if (invocation.TargetMethod.Name != "Parse") + { + return; + } + + string title = MicrosoftNetCoreAnalyzersResources.PreferJsonElementParseFix; + context.RegisterCodeFix( + CodeAction.Create( + title, + createChangedDocument: async ct => + { + DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + SyntaxGenerator generator = editor.Generator; + + // Get the JsonElement type + INamedTypeSymbol? jsonElementType = model.Compilation.GetOrCreateTypeByMetadataName("System.Text.Json.JsonElement"); + if (jsonElementType == null) + { + return doc; + } + + // Create the replacement: JsonElement.Parse(...) + // We need to use the same arguments that were passed to JsonDocument.Parse + var arguments = invocation.Arguments.Select(arg => arg.Syntax).ToArray(); + + SyntaxNode memberAccess = generator.MemberAccessExpression( + generator.TypeExpressionForStaticMemberAccess(jsonElementType), + "Parse"); + + SyntaxNode replacement = generator.InvocationExpression(memberAccess, arguments); + + // Replace the entire property reference (JsonDocument.Parse(...).RootElement) with JsonElement.Parse(...) + editor.ReplaceNode(propertyReference.Syntax, replacement.WithTriviaFrom(propertyReference.Syntax)); + + return editor.GetChangedDocument(); + }, + equivalenceKey: title), + context.Diagnostics); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs new file mode 100644 index 000000000000..260dbb76abe7 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA2026: Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferJsonElementParse : DiagnosticAnalyzer + { + internal const string RuleId = "CA2026"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferJsonElementParseTitle)), + CreateLocalizableResourceString(nameof(PreferJsonElementParseMessage)), + DiagnosticCategory.Reliability, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(PreferJsonElementParseDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(context => + { + // Get the JsonDocument and JsonElement types + INamedTypeSymbol? jsonDocumentType = context.Compilation.GetOrCreateTypeByMetadataName("System.Text.Json.JsonDocument"); + INamedTypeSymbol? jsonElementType = context.Compilation.GetOrCreateTypeByMetadataName("System.Text.Json.JsonElement"); + + if (jsonDocumentType is null || jsonElementType is null) + { + return; + } + + // Check if JsonElement.Parse exists (available in .NET 10+) + IMethodSymbol? jsonElementParse = null; + foreach (var member in jsonElementType.GetMembers("Parse")) + { + if (member is IMethodSymbol method && method.IsStatic) + { + jsonElementParse = method; + break; + } + } + + if (jsonElementParse is null) + { + // JsonElement.Parse doesn't exist, so no need to suggest it + return; + } + + // Get the JsonDocument.Parse methods + IMethodSymbol? jsonDocumentParseMethod = null; + foreach (var member in jsonDocumentType.GetMembers("Parse")) + { + if (member is IMethodSymbol method && method.IsStatic) + { + jsonDocumentParseMethod = method; + break; + } + } + + if (jsonDocumentParseMethod is null) + { + return; + } + + // Get the RootElement property + IPropertySymbol? rootElementProperty = null; + foreach (var member in jsonDocumentType.GetMembers("RootElement")) + { + if (member is IPropertySymbol property) + { + rootElementProperty = property; + break; + } + } + + if (rootElementProperty is null) + { + return; + } + + context.RegisterOperationAction(context => + { + var propertyReference = (IPropertyReferenceOperation)context.Operation; + + // Check if this is accessing the RootElement property + if (!SymbolEqualityComparer.Default.Equals(propertyReference.Property, rootElementProperty)) + { + return; + } + + // Check if the instance is a direct call to JsonDocument.Parse + if (propertyReference.Instance is not IInvocationOperation invocation) + { + return; + } + + if (!SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, jsonDocumentType)) + { + return; + } + + if (invocation.TargetMethod.Name != "Parse") + { + return; + } + + // Now we have the pattern: JsonDocument.Parse(...).RootElement + // Check if the JsonDocument is disposed. We'll look for patterns where it's immediately + // accessed and not stored, which is the primary concern. + + // If the parent operation is an assignment to a variable of type JsonElement, + // and the JsonDocument is never stored, this is the problematic pattern. + if (IsImmediateUseWithoutDisposal(propertyReference)) + { + context.ReportDiagnostic(propertyReference.CreateDiagnostic(Rule)); + } + }, OperationKind.PropertyReference); + }); + } + + private static bool IsImmediateUseWithoutDisposal(IPropertyReferenceOperation propertyReference) + { + // The pattern we're looking for is: + // JsonElement element = JsonDocument.Parse("json").RootElement; + // + // In this case, the propertyReference is the .RootElement access, + // and its parent should be something that uses it directly without + // the JsonDocument being stored or disposed. + + // If we walk up the tree and never find the JsonDocument being stored in a variable + // or being used in a using statement, then it's not being disposed properly. + + // For simplicity, we'll flag any case where: + // 1. The property reference is the direct result of JsonDocument.Parse() + // 2. The result is not part of a using declaration/statement + + IOperation? current = propertyReference.Parent; + + // Walk up to find if this is within a using statement/declaration + while (current != null) + { + if (current is IUsingOperation) + { + // It's within a using statement, so it's being disposed + return false; + } + + // Check for using declaration + if (current is IUsingDeclarationOperation) + { + // It's a using declaration, so it's being disposed + return false; + } + + current = current.Parent; + } + + // Not properly disposed + return true; + } + + private static bool ContainsOperation(IOperation? parent, IOperation child) + { + if (parent == null) + { + return false; + } + + if (parent == child) + { + return true; + } + + foreach (var descendant in parent.DescendantsAndSelf()) + { + if (descendant == child) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index d5f92baf02e5..547cfec47e45 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2238,6 +2238,26 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj Upřednostňovat IsEmpty před Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Upřednostňujte použití vlastnosti IsEmpty, Count nebo Length podle toho, která je k dispozici, namísto volání metody Enumerable.Any(). Záměr je jasnější a je výkonnější než použití rozšiřující metody Enumerable.Any(). diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index f63f006d064d..a13fa704ba8f 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2238,6 +2238,26 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Ziehen Sie "IsEmpty" gegenüber "Count" vor + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Verwenden Sie lieber die Eigenschaften „IsEmpty“, „Count“ oder „Length“, sofern verfügbar, statt „Enumerable.Any()“ aufzurufen. Die Absicht ist klarer und das Ausführungsverhalten besser als bei Verwendung der Erweiterungsmethode „Enumerable.Any()“. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index a03a7a0fa4d0..1c86fe791114 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2238,6 +2238,26 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Elegir IsEmpty en vez de Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Es preferible usar las propiedades "IsEmpty", "Count" o "Length", si hay alguna disponible, en lugar de llamar a "Enumerable.Any()". La intención es más clara y tiene un mejor rendimiento que usar el método de extensión "Enumerable.Any()". diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 40c160bb5b5f..36c5534b8962 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2238,6 +2238,26 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Préférer IsEmpty à Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Préférez utiliser les propriétés 'IsEmpty', 'Count' ou 'Length' selon la disponibilité, plutôt que d’appeler 'Enumerable.Any()'. L’intention est plus claire et plus performante que l’utilisation de la méthode d’extension 'Enumerable.Any()'. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 924a482d9f5d..6c3d97692a3c 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2238,6 +2238,26 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Preferire IsEmpty a Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Preferire l’uso delle proprietà 'IsEmpty', 'Count' o 'Length' se disponibili, anziché chiamare 'Enumerable.Any()'. La finalità è più chiara ed è più efficiente rispetto all'uso del metodo di estensione 'Enumerable.Any()'. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6c3fbfeccd9a..8c4aa8a20ea1 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2238,6 +2238,26 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( Count より IsEmpty を優先する + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 'Enumerable.Any()' を呼び出すのではなく、'IsEmpty'、'Count'、または 'Length' のいずれか使用可能なプロパティの使用を優先してください。この方が、'Enumerable.Any()' 拡張メソッドを使用するよりも意図が明確で、パフォーマンスが向上します。 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index f0706403c0d4..47929d1c21f5 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2238,6 +2238,26 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' Count 대신 IsEmpty 사용 + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 'Enumerable.Any()'를 호출하는 것보다 사용 가능한 'IsEmpty', 'Count' 또는 'Length' 속성을 사용하는 것이 좋습니다. 의도가 더 명확하고 'Enumerable.Any()' 확장 방법을 사용하는 것보다 더 성능이 좋습니다. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 51f4dee3e919..f8c701db54b3 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2238,6 +2238,26 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Preferuj właściwość IsEmpty przed Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Preferuj używanie właściwości „IsEmpty”, „Count” lub „Length” w zależności od dostępności, zamiast wywoływać metodę „Enumerable.Any()”. Intencja jest bardziej przejrzysta i wydajniejsza niż użycie metody rozszerzenia „Enumerable.Any()”. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index cb9dea8edaf1..71cde1d01b0c 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2238,6 +2238,26 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com Preferir IsEmpty em vez de Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Prefira usar as propriedades 'IsEmpty', 'Count' ou 'Length', conforme disponível, em vez de chamar 'Enumerable.Any()'. A intenção é mais clara e tem mais desempenho do que usar o método de extensão 'Enumerable.Any()'. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 57538a5cb231..355273e1bf08 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2238,6 +2238,26 @@ Widening and user defined conversions are not supported with generic types.Старайтесь использовать "IsEmpty" вместо "Count" + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Старайтесь использовать свойства 'IsEmpty', 'Count' или 'Length' в зависимости от доступности вместо вызова 'Enumerable.Any()'. Намерение является более четким и более эффективным по сравнению с использованием метода расширения 'Enumerable.Any()'. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index de2ea9f502e1..33786bf42bc7 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2238,6 +2238,26 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Count yerine IsEmpty tercih edin + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 'Enumerable.Any()' metodunu çağırmak yerine hangisi mevcutsa 'IsEmpty', 'Count' veya 'Length' özelliklerinden birini kullanmayı tercih edin. Amaç daha kolay anlaşılır ve 'Enumerable.Any()' genişletme metoduna göre daha yüksek performans sunar. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 68f722d64e4f..c9ff6d540e03 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2238,6 +2238,26 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin 最好使用 "IsEmpty" (而不是 "Count") + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 首选使用可用的 'IsEmpty'、'Count' 或 'Length' 属性,而不是调用 'Enumerable.Any()'。意向更清晰,其性能高于使用 'Enumerable.Any() 扩展方法。 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 43db515c0d4b..96185b9b1822 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2238,6 +2238,26 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 建議使用 IsEmpty,而不要使用 Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 偏好使用 'IsEmpty'、'Count' 或 'Length' 屬性 (以可用者為准),而不是呼叫 'Enumerable.Any()'。意圖更清楚,而且比使用 'Enumerable.Any()' 擴充方法更具效能。 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 84d171e834bd..14da2a1ba4bd 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -18,7 +18,7 @@ Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265 Naming: CA1700-CA1727 Interoperability: CA1400-CA1422 Maintainability: CA1500-CA1515 -Reliability: CA9998-CA9999, CA2000-CA2025 +Reliability: CA9998-CA9999, CA2000-CA2026 Documentation: CA1200-CA1200 # Microsoft CodeAnalysis API rules diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs new file mode 100644 index 000000000000..d40f468211a5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs @@ -0,0 +1,403 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.PreferJsonElementParse, + Microsoft.NetCore.Analyzers.Runtime.PreferJsonElementParseFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class PreferJsonElementParseTests + { + [Fact] + public async Task NoDiagnostic_WhenJsonElementParseNotAvailable() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement { } +} + +class Test +{ + void M() + { + JsonElement element = JsonDocument.Parse(""json"").RootElement; + } +} +"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Diagnostic_WhenUsingJsonDocumentParseRootElement() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + JsonElement element = [|JsonDocument.Parse(""json"").RootElement|]; + } +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + JsonElement element = JsonElement.Parse(""json""); + } +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_WhenUsingJsonDocumentParseRootElement_WithMultipleArguments() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } +} + +class Test +{ + void M() + { + JsonElement element = [|JsonDocument.Parse(""json"", default).RootElement|]; + } +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } +} + +class Test +{ + void M() + { + JsonElement element = JsonElement.Parse(""json"", default); + } +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NoDiagnostic_WhenJsonDocumentProperlyDisposed_UsingStatement() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + using (var doc = JsonDocument.Parse(""json"")) + { + JsonElement element = doc.RootElement; + } + } +} +"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NoDiagnostic_WhenJsonDocumentProperlyDisposed_UsingDeclaration() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + using var doc = JsonDocument.Parse(""json""); + JsonElement element = doc.RootElement; + } +} +"; + await new VerifyCS.Test + { + TestCode = source, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8 + }.RunAsync(); + } + + [Fact] + public async Task Diagnostic_InlineAccess_DirectAssignment() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M(string json) + { + var element = [|JsonDocument.Parse(json).RootElement|]; + } +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M(string json) + { + var element = JsonElement.Parse(json); + } +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_ReturnStatement() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + JsonElement M(string json) + { + return [|JsonDocument.Parse(json).RootElement|]; + } +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + JsonElement M(string json) + { + return JsonElement.Parse(json); + } +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_ExpressionBodiedMember() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + JsonElement M(string json) => [|JsonDocument.Parse(json).RootElement|]; +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + JsonElement M(string json) => JsonElement.Parse(json); +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + } +} From 90c105eaf6fe66757b013ee84fd81d9b4738ad02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:15:45 +0000 Subject: [PATCH 3/8] Run dotnet format to clean up whitespace --- .../Runtime/PreferJsonElementParse.Fixer.cs | 4 ++-- .../Runtime/PreferJsonElementParse.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs index 5873fe3934e6..c23d5af4ce97 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs @@ -79,11 +79,11 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) // Create the replacement: JsonElement.Parse(...) // We need to use the same arguments that were passed to JsonDocument.Parse var arguments = invocation.Arguments.Select(arg => arg.Syntax).ToArray(); - + SyntaxNode memberAccess = generator.MemberAccessExpression( generator.TypeExpressionForStaticMemberAccess(jsonElementType), "Parse"); - + SyntaxNode replacement = generator.InvocationExpression(memberAccess, arguments); // Replace the entire property reference (JsonDocument.Parse(...).RootElement) with JsonElement.Parse(...) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs index 260dbb76abe7..01b5596add2e 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs @@ -125,7 +125,7 @@ public override void Initialize(AnalysisContext context) // Now we have the pattern: JsonDocument.Parse(...).RootElement // Check if the JsonDocument is disposed. We'll look for patterns where it's immediately // accessed and not stored, which is the primary concern. - + // If the parent operation is an assignment to a variable of type JsonElement, // and the JsonDocument is never stored, this is the problematic pattern. if (IsImmediateUseWithoutDisposal(propertyReference)) @@ -147,13 +147,13 @@ private static bool IsImmediateUseWithoutDisposal(IPropertyReferenceOperation pr // If we walk up the tree and never find the JsonDocument being stored in a variable // or being used in a using statement, then it's not being disposed properly. - + // For simplicity, we'll flag any case where: // 1. The property reference is the direct result of JsonDocument.Parse() // 2. The result is not part of a using declaration/statement - + IOperation? current = propertyReference.Parent; - + // Walk up to find if this is within a using statement/declaration while (current != null) { @@ -162,14 +162,14 @@ private static bool IsImmediateUseWithoutDisposal(IPropertyReferenceOperation pr // It's within a using statement, so it's being disposed return false; } - + // Check for using declaration if (current is IUsingDeclarationOperation) { // It's a using declaration, so it's being disposed return false; } - + current = current.Parent; } From b4387d16e794a7e84ce1aa08b7f0d227f0582464 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 19:44:03 +0000 Subject: [PATCH 4/8] Regenerate auto-generated documentation files for CA2026 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 +++++++++++ .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md index facf822421c6..fd1fcef90923 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -2154,6 +2154,18 @@ Unawaited tasks that use 'IDisposable' instances may use those instances long af |CodeFix|False| --- +## [CA2026](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026): Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + +JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + +|Item|Value| +|-|-| +|Category|Reliability| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2100](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100): Review SQL queries for security vulnerabilities SQL queries that directly use user input can be vulnerable to SQL injection attacks. Review this SQL query for potential vulnerabilities, and consider using a parameterized SQL query. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif index a3e0dd7859aa..f36c59791cd4 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3850,6 +3850,26 @@ ] } }, + "CA2026": { + "id": "CA2026", + "shortDescription": "Prefer JsonElement.Parse over JsonDocument.Parse().RootElement", + "fullDescription": "JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026", + "properties": { + "category": "Reliability", + "isEnabledByDefault": true, + "typeName": "PreferJsonElementParse", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2100": { "id": "CA2100", "shortDescription": "Review SQL queries for security vulnerabilities", From 3629e72c3b6571e4d3d5670ee3ed159a32c13e60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 22:53:20 +0000 Subject: [PATCH 5/8] Refactor analyzer based on code review feedback - Use WellKnownTypeNames for type strings - Replace open-coded loops with LINQ - Remove unnecessary disposal checks (if RootElement is accessed directly off Parse, JsonDocument is never disposed) - Delete unused ContainsOperation and IsImmediateUseWithoutDisposal methods - Only raise diagnostic when JsonElement.Parse has matching overload with same parameter types Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Runtime/PreferJsonElementParse.Fixer.cs | 2 +- .../Runtime/PreferJsonElementParse.cs | 133 +++--------------- .../Utilities/Compiler/WellKnownTypeNames.cs | 2 + 3 files changed, 26 insertions(+), 111 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs index c23d5af4ce97..ad72f630a866 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs @@ -70,7 +70,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) SyntaxGenerator generator = editor.Generator; // Get the JsonElement type - INamedTypeSymbol? jsonElementType = model.Compilation.GetOrCreateTypeByMetadataName("System.Text.Json.JsonElement"); + INamedTypeSymbol? jsonElementType = model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonJsonElement); if (jsonElementType == null) { return doc; diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs index 01b5596add2e..8f044f9a2395 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; @@ -39,63 +40,37 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(context => { // Get the JsonDocument and JsonElement types - INamedTypeSymbol? jsonDocumentType = context.Compilation.GetOrCreateTypeByMetadataName("System.Text.Json.JsonDocument"); - INamedTypeSymbol? jsonElementType = context.Compilation.GetOrCreateTypeByMetadataName("System.Text.Json.JsonElement"); + INamedTypeSymbol? jsonDocumentType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonJsonDocument); + INamedTypeSymbol? jsonElementType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonJsonElement); if (jsonDocumentType is null || jsonElementType is null) { return; } - // Check if JsonElement.Parse exists (available in .NET 10+) - IMethodSymbol? jsonElementParse = null; - foreach (var member in jsonElementType.GetMembers("Parse")) - { - if (member is IMethodSymbol method && method.IsStatic) - { - jsonElementParse = method; - break; - } - } - - if (jsonElementParse is null) - { - // JsonElement.Parse doesn't exist, so no need to suggest it - return; - } - - // Get the JsonDocument.Parse methods - IMethodSymbol? jsonDocumentParseMethod = null; - foreach (var member in jsonDocumentType.GetMembers("Parse")) - { - if (member is IMethodSymbol method && method.IsStatic) - { - jsonDocumentParseMethod = method; - break; - } - } - - if (jsonDocumentParseMethod is null) + // Check if JsonElement.Parse and JsonDocument.Parse exist + if (!jsonElementType.GetMembers("Parse").Any(m => m is IMethodSymbol { IsStatic: true }) || + !jsonDocumentType.GetMembers("Parse").Any(m => m is IMethodSymbol { IsStatic: true })) { return; } // Get the RootElement property - IPropertySymbol? rootElementProperty = null; - foreach (var member in jsonDocumentType.GetMembers("RootElement")) - { - if (member is IPropertySymbol property) - { - rootElementProperty = property; - break; - } - } + IPropertySymbol? rootElementProperty = jsonDocumentType.GetMembers("RootElement") + .OfType() + .FirstOrDefault(); if (rootElementProperty is null) { return; } + // Get all JsonElement.Parse overloads for matching + var jsonElementParseOverloads = jsonElementType.GetMembers("Parse") + .OfType() + .Where(m => m.IsStatic) + .ToImmutableArray(); + context.RegisterOperationAction(context => { var propertyReference = (IPropertyReferenceOperation)context.Operation; @@ -123,81 +98,19 @@ public override void Initialize(AnalysisContext context) } // Now we have the pattern: JsonDocument.Parse(...).RootElement - // Check if the JsonDocument is disposed. We'll look for patterns where it's immediately - // accessed and not stored, which is the primary concern. - - // If the parent operation is an assignment to a variable of type JsonElement, - // and the JsonDocument is never stored, this is the problematic pattern. - if (IsImmediateUseWithoutDisposal(propertyReference)) + // Check if there's a matching JsonElement.Parse overload with the same parameter types + var jsonDocumentParseMethod = invocation.TargetMethod; + bool hasMatchingOverload = jsonElementParseOverloads.Any(elementParse => + elementParse.Parameters.Length == jsonDocumentParseMethod.Parameters.Length && + elementParse.Parameters.Zip(jsonDocumentParseMethod.Parameters, (p1, p2) => + SymbolEqualityComparer.Default.Equals(p1.Type, p2.Type)).All(match => match)); + + if (hasMatchingOverload) { context.ReportDiagnostic(propertyReference.CreateDiagnostic(Rule)); } }, OperationKind.PropertyReference); }); } - - private static bool IsImmediateUseWithoutDisposal(IPropertyReferenceOperation propertyReference) - { - // The pattern we're looking for is: - // JsonElement element = JsonDocument.Parse("json").RootElement; - // - // In this case, the propertyReference is the .RootElement access, - // and its parent should be something that uses it directly without - // the JsonDocument being stored or disposed. - - // If we walk up the tree and never find the JsonDocument being stored in a variable - // or being used in a using statement, then it's not being disposed properly. - - // For simplicity, we'll flag any case where: - // 1. The property reference is the direct result of JsonDocument.Parse() - // 2. The result is not part of a using declaration/statement - - IOperation? current = propertyReference.Parent; - - // Walk up to find if this is within a using statement/declaration - while (current != null) - { - if (current is IUsingOperation) - { - // It's within a using statement, so it's being disposed - return false; - } - - // Check for using declaration - if (current is IUsingDeclarationOperation) - { - // It's a using declaration, so it's being disposed - return false; - } - - current = current.Parent; - } - - // Not properly disposed - return true; - } - - private static bool ContainsOperation(IOperation? parent, IOperation child) - { - if (parent == null) - { - return false; - } - - if (parent == child) - { - return true; - } - - foreach (var descendant in parent.DescendantsAndSelf()) - { - if (descendant == child) - { - return true; - } - } - - return false; - } } } diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs index a733c059885b..af724317d84f 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -438,6 +438,8 @@ internal static class WellKnownTypeNames public const string SystemSystemException = "System.SystemException"; public const string SystemTextCompositeFormat = "System.Text.CompositeFormat"; public const string SystemTextEncoding = "System.Text.Encoding"; + public const string SystemTextJsonJsonDocument = "System.Text.Json.JsonDocument"; + public const string SystemTextJsonJsonElement = "System.Text.Json.JsonElement"; public const string SystemTextJsonJsonSerializerOptions = "System.Text.Json.JsonSerializerOptions"; public const string SystemTextJsonJsonSerializer = "System.Text.Json.JsonSerializer"; public const string SystemTextRegularExpressionsGroup = "System.Text.RegularExpressions.Group"; From a30e77b06fa0498f0ec6e1d39f2a3cff9209bc3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Oct 2025 02:59:25 +0000 Subject: [PATCH 6/8] Optimize analyzer performance and avoid lambda captures - Move jsonElementParseOverloads collection up and use it to check if JsonElement.Parse exists - Replace LINQ-based parameter matching with loop to avoid lambda captures and enable early exit - Improves performance by eliminating unnecessary allocations Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Runtime/PreferJsonElementParse.cs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs index 8f044f9a2395..a4a5c3e2a2b5 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs @@ -48,8 +48,14 @@ public override void Initialize(AnalysisContext context) return; } - // Check if JsonElement.Parse and JsonDocument.Parse exist - if (!jsonElementType.GetMembers("Parse").Any(m => m is IMethodSymbol { IsStatic: true }) || + // Get all JsonElement.Parse overloads for matching + var jsonElementParseOverloads = jsonElementType.GetMembers("Parse") + .OfType() + .Where(m => m.IsStatic) + .ToImmutableArray(); + + // Check if JsonElement.Parse exists + if (jsonElementParseOverloads.IsEmpty || !jsonDocumentType.GetMembers("Parse").Any(m => m is IMethodSymbol { IsStatic: true })) { return; @@ -65,12 +71,6 @@ public override void Initialize(AnalysisContext context) return; } - // Get all JsonElement.Parse overloads for matching - var jsonElementParseOverloads = jsonElementType.GetMembers("Parse") - .OfType() - .Where(m => m.IsStatic) - .ToImmutableArray(); - context.RegisterOperationAction(context => { var propertyReference = (IPropertyReferenceOperation)context.Operation; @@ -100,14 +100,29 @@ public override void Initialize(AnalysisContext context) // Now we have the pattern: JsonDocument.Parse(...).RootElement // Check if there's a matching JsonElement.Parse overload with the same parameter types var jsonDocumentParseMethod = invocation.TargetMethod; - bool hasMatchingOverload = jsonElementParseOverloads.Any(elementParse => - elementParse.Parameters.Length == jsonDocumentParseMethod.Parameters.Length && - elementParse.Parameters.Zip(jsonDocumentParseMethod.Parameters, (p1, p2) => - SymbolEqualityComparer.Default.Equals(p1.Type, p2.Type)).All(match => match)); - if (hasMatchingOverload) + foreach (var elementParseOverload in jsonElementParseOverloads) { - context.ReportDiagnostic(propertyReference.CreateDiagnostic(Rule)); + if (elementParseOverload.Parameters.Length != jsonDocumentParseMethod.Parameters.Length) + { + continue; + } + + bool parametersMatch = true; + for (int i = 0; i < elementParseOverload.Parameters.Length; i++) + { + if (!SymbolEqualityComparer.Default.Equals(elementParseOverload.Parameters[i].Type, jsonDocumentParseMethod.Parameters[i].Type)) + { + parametersMatch = false; + break; + } + } + + if (parametersMatch) + { + context.ReportDiagnostic(propertyReference.CreateDiagnostic(Rule)); + break; + } } }, OperationKind.PropertyReference); }); From b092256d2e7f71bfc2385eaa132539bf5d266220 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:40:16 +0000 Subject: [PATCH 7/8] Consolidate validation checks and add comprehensive tests - Consolidate multiple if checks into single conditions in both analyzer and fixer - Add test for case where JsonElement.Parse has no matching overload - Add test for chained property access after RootElement - Add test to verify no diagnostic when RootElement accessed via variable - Add test for multiple parameters with matching overload - Add test for pattern in lambda expressions - All 13 tests now passing Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Runtime/PreferJsonElementParse.Fixer.cs | 30 +- .../Runtime/PreferJsonElementParse.cs | 23 +- .../Runtime/PreferJsonElementParseTests.cs | 258 ++++++++++++++++++ 3 files changed, 268 insertions(+), 43 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs index ad72f630a866..7dddc3acbc5a 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs @@ -31,31 +31,11 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) SemanticModel model = await doc.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - SyntaxNode node = root.FindNode(context.Span, getInnermostNodeForTie: true); - if (node == null) - { - return; - } - - IOperation? operation = model.GetOperation(node, context.CancellationToken); - if (operation is not IPropertyReferenceOperation propertyReference) - { - return; - } - - // Verify this is the RootElement property access - if (propertyReference.Property.Name != "RootElement") - { - return; - } - - // Verify the instance is an invocation to JsonDocument.Parse - if (propertyReference.Instance is not IInvocationOperation invocation) - { - return; - } - - if (invocation.TargetMethod.Name != "Parse") + if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode node || + model.GetOperation(node, context.CancellationToken) is not IPropertyReferenceOperation propertyReference || + propertyReference.Property.Name != "RootElement" || + propertyReference.Instance is not IInvocationOperation invocation || + invocation.TargetMethod.Name != "Parse") { return; } diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs index a4a5c3e2a2b5..2e509f70dfda 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs @@ -75,24 +75,11 @@ public override void Initialize(AnalysisContext context) { var propertyReference = (IPropertyReferenceOperation)context.Operation; - // Check if this is accessing the RootElement property - if (!SymbolEqualityComparer.Default.Equals(propertyReference.Property, rootElementProperty)) - { - return; - } - - // Check if the instance is a direct call to JsonDocument.Parse - if (propertyReference.Instance is not IInvocationOperation invocation) - { - return; - } - - if (!SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, jsonDocumentType)) - { - return; - } - - if (invocation.TargetMethod.Name != "Parse") + // Check if this is accessing the RootElement property and the instance is a direct call to JsonDocument.Parse + if (!SymbolEqualityComparer.Default.Equals(propertyReference.Property, rootElementProperty) || + propertyReference.Instance is not IInvocationOperation invocation || + !SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, jsonDocumentType) || + invocation.TargetMethod.Name != "Parse") { return; } diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs index d40f468211a5..fab21a0bf721 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs @@ -396,6 +396,264 @@ class Test { JsonElement M(string json) => JsonElement.Parse(json); } +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NoDiagnostic_WhenJsonElementParseHasNoMatchingOverload() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + // JsonElement.Parse only has single string parameter, not the overload with options + public static JsonElement Parse(string json) => default; + } + + public struct JsonDocumentOptions { } +} + +class Test +{ + void M() + { + // This should NOT produce a diagnostic because JsonElement.Parse doesn't have an overload with JsonDocumentOptions + JsonElement element = JsonDocument.Parse(""json"", default(JsonDocumentOptions)).RootElement; + } +} +"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Diagnostic_WhenChainedWithOtherOperations() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + public JsonValueKind ValueKind => default; + } + + public enum JsonValueKind { } +} + +class Test +{ + void M() + { + // Should produce diagnostic even when chained with property access + var kind = [|JsonDocument.Parse(""json"").RootElement|].ValueKind; + } +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + public JsonValueKind ValueKind => default; + } + + public enum JsonValueKind { } +} + +class Test +{ + void M() + { + // Should produce diagnostic even when chained with property access + var kind = JsonElement.Parse(""json"").ValueKind; + } +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NoDiagnostic_WhenRootElementAccessedViaVariable() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + // Not the pattern we're looking for - JsonDocument is stored in a variable + var doc = JsonDocument.Parse(""json""); + JsonElement element = doc.RootElement; + } +} +"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Diagnostic_MultipleParametersWithMatchingOverload() + { + var source = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } +} + +class Test +{ + void M() + { + JsonElement element = [|JsonDocument.Parse(""json"", default).RootElement|]; + } +} +"; + var fixedSource = @" +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } +} + +class Test +{ + void M() + { + JsonElement element = JsonElement.Parse(""json"", default); + } +} +"; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_InLambdaExpression() + { + var source = @" +using System; +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + Func parser = json => [|JsonDocument.Parse(json).RootElement|]; + } +} +"; + var fixedSource = @" +using System; +using System.Text.Json; + +namespace System.Text.Json +{ + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } +} + +class Test +{ + void M() + { + Func parser = json => JsonElement.Parse(json); + } +} "; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } From 33b84f84378167f909fbb885508b014ed26a411b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:24:27 +0000 Subject: [PATCH 8/8] Convert test strings from verbatim to raw string literals - Replace all @"..." verbatim string literals with """ ... """ raw string literals - Properly indent all test code for better readability - Makes tests easier to read and maintain - All 13 tests continue to pass Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Runtime/PreferJsonElementParseTests.cs | 1130 ++++++++--------- 1 file changed, 565 insertions(+), 565 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs index fab21a0bf721..aed9241165a0 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs @@ -14,217 +14,217 @@ public class PreferJsonElementParseTests [Fact] public async Task NoDiagnostic_WhenJsonElementParseNotAvailable() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement { } -} - -class Test -{ - void M() - { - JsonElement element = JsonDocument.Parse(""json"").RootElement; - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement { } + } + + class Test + { + void M() + { + JsonElement element = JsonDocument.Parse("json").RootElement; + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] public async Task Diagnostic_WhenUsingJsonDocumentParseRootElement() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - JsonElement element = [|JsonDocument.Parse(""json"").RootElement|]; - } -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - JsonElement element = JsonElement.Parse(""json""); - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + JsonElement element = [|JsonDocument.Parse("json").RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + JsonElement element = JsonElement.Parse("json"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task Diagnostic_WhenUsingJsonDocumentParseRootElement_WithMultipleArguments() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json, JsonDocumentOptions options) => default; - } - - public struct JsonDocumentOptions { } -} - -class Test -{ - void M() - { - JsonElement element = [|JsonDocument.Parse(""json"", default).RootElement|]; - } -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json, JsonDocumentOptions options) => default; - } - - public struct JsonDocumentOptions { } -} - -class Test -{ - void M() - { - JsonElement element = JsonElement.Parse(""json"", default); - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = [|JsonDocument.Parse("json", default).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = JsonElement.Parse("json", default); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task NoDiagnostic_WhenJsonDocumentProperlyDisposed_UsingStatement() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - using (var doc = JsonDocument.Parse(""json"")) - { - JsonElement element = doc.RootElement; - } - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + using (var doc = JsonDocument.Parse("json")) + { + JsonElement element = doc.RootElement; + } + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] public async Task NoDiagnostic_WhenJsonDocumentProperlyDisposed_UsingDeclaration() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - using var doc = JsonDocument.Parse(""json""); - JsonElement element = doc.RootElement; - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + using var doc = JsonDocument.Parse("json"); + JsonElement element = doc.RootElement; + } + } + """; await new VerifyCS.Test { TestCode = source, @@ -235,426 +235,426 @@ void M() [Fact] public async Task Diagnostic_InlineAccess_DirectAssignment() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M(string json) - { - var element = [|JsonDocument.Parse(json).RootElement|]; - } -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M(string json) - { - var element = JsonElement.Parse(json); - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M(string json) + { + var element = [|JsonDocument.Parse(json).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M(string json) + { + var element = JsonElement.Parse(json); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task Diagnostic_ReturnStatement() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - JsonElement M(string json) - { - return [|JsonDocument.Parse(json).RootElement|]; - } -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - JsonElement M(string json) - { - return JsonElement.Parse(json); - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) + { + return [|JsonDocument.Parse(json).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) + { + return JsonElement.Parse(json); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task Diagnostic_ExpressionBodiedMember() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - JsonElement M(string json) => [|JsonDocument.Parse(json).RootElement|]; -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - JsonElement M(string json) => JsonElement.Parse(json); -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) => [|JsonDocument.Parse(json).RootElement|]; + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) => JsonElement.Parse(json); + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task NoDiagnostic_WhenJsonElementParseHasNoMatchingOverload() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - // JsonElement.Parse only has single string parameter, not the overload with options - public static JsonElement Parse(string json) => default; - } - - public struct JsonDocumentOptions { } -} - -class Test -{ - void M() - { - // This should NOT produce a diagnostic because JsonElement.Parse doesn't have an overload with JsonDocumentOptions - JsonElement element = JsonDocument.Parse(""json"", default(JsonDocumentOptions)).RootElement; - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + // JsonElement.Parse only has single string parameter, not the overload with options + public static JsonElement Parse(string json) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + // This should NOT produce a diagnostic because JsonElement.Parse doesn't have an overload with JsonDocumentOptions + JsonElement element = JsonDocument.Parse("json", default(JsonDocumentOptions)).RootElement; + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] public async Task Diagnostic_WhenChainedWithOtherOperations() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - public JsonValueKind ValueKind => default; - } - - public enum JsonValueKind { } -} - -class Test -{ - void M() - { - // Should produce diagnostic even when chained with property access - var kind = [|JsonDocument.Parse(""json"").RootElement|].ValueKind; - } -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - public JsonValueKind ValueKind => default; - } - - public enum JsonValueKind { } -} - -class Test -{ - void M() - { - // Should produce diagnostic even when chained with property access - var kind = JsonElement.Parse(""json"").ValueKind; - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + public JsonValueKind ValueKind => default; + } + + public enum JsonValueKind { } + } + + class Test + { + void M() + { + // Should produce diagnostic even when chained with property access + var kind = [|JsonDocument.Parse("json").RootElement|].ValueKind; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + public JsonValueKind ValueKind => default; + } + + public enum JsonValueKind { } + } + + class Test + { + void M() + { + // Should produce diagnostic even when chained with property access + var kind = JsonElement.Parse("json").ValueKind; + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task NoDiagnostic_WhenRootElementAccessedViaVariable() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - // Not the pattern we're looking for - JsonDocument is stored in a variable - var doc = JsonDocument.Parse(""json""); - JsonElement element = doc.RootElement; - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + // Not the pattern we're looking for - JsonDocument is stored in a variable + var doc = JsonDocument.Parse("json"); + JsonElement element = doc.RootElement; + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] public async Task Diagnostic_MultipleParametersWithMatchingOverload() { - var source = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json, JsonDocumentOptions options) => default; - } - - public struct JsonDocumentOptions { } -} - -class Test -{ - void M() - { - JsonElement element = [|JsonDocument.Parse(""json"", default).RootElement|]; - } -} -"; - var fixedSource = @" -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json, JsonDocumentOptions options) => default; - } - - public struct JsonDocumentOptions { } -} - -class Test -{ - void M() - { - JsonElement element = JsonElement.Parse(""json"", default); - } -} -"; + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = [|JsonDocument.Parse("json", default).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = JsonElement.Parse("json", default); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } [Fact] public async Task Diagnostic_InLambdaExpression() { - var source = @" -using System; -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - Func parser = json => [|JsonDocument.Parse(json).RootElement|]; - } -} -"; - var fixedSource = @" -using System; -using System.Text.Json; - -namespace System.Text.Json -{ - public sealed class JsonDocument : System.IDisposable - { - public static JsonDocument Parse(string json) => null; - public JsonElement RootElement => default; - public void Dispose() { } - } - - public struct JsonElement - { - public static JsonElement Parse(string json) => default; - } -} - -class Test -{ - void M() - { - Func parser = json => JsonElement.Parse(json); - } -} -"; + var source = """ + using System; + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + Func parser = json => [|JsonDocument.Parse(json).RootElement|]; + } + } + """; + var fixedSource = """ + using System; + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + Func parser = json => JsonElement.Parse(json); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } }