Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace HotChocolate;

/// <summary>
/// Controls whether XML documentation comments are considered when inferring GraphQL descriptions.
/// </summary>
/// <remarks>
/// By default, Hot Chocolate infers GraphQL descriptions from XML documentation comments.
/// Applying this attribute allows explicitly opting out of that behavior.
/// When applied at the assembly or type level, the setting applies to all contained members
/// unless overridden by a more specific declaration.
/// This attribute can also be used to explicitly opt back in by setting <see cref="Ignore"/> to <c>false</c>.
/// </remarks>
[AttributeUsage(
AttributeTargets.Class
| AttributeTargets.Struct
| AttributeTargets.Interface
| AttributeTargets.Method
| AttributeTargets.Property
| AttributeTargets.Parameter
| AttributeTargets.Field
| AttributeTargets.Assembly)]
public sealed class GraphQLIgnoreXmlDocumentationAttribute : Attribute
{
/// <summary>
/// Gets or sets whether the XML documentation should be ignored for the annotated symbol.
/// </summary>
/// <remarks>
/// The default value is <c>true</c>, which disables XML documentation inference.
/// Set this value to <c>false</c> to explicitly enable XML documentation inference
/// when it has been disabled within the outer scope.
/// </remarks>
public bool Ignore { get; init; } = true;
}
13 changes: 13 additions & 0 deletions src/HotChocolate/Core/src/Types.Analyzers/Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,17 @@ public static class Errors
category: "TypeSystem",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor MissingXmlDocumentation =
new(
id: "HC0106",
title: "XML documentation is not enabled",
messageFormat:
"XML documentation inference is enabled, but no XML documentation file was generated. "
+ "Enable XML documentation generation within the current project"
+ "or add [assembly: GraphQLIgnoreXmlDocumentation] to disable XML documentation inference.",
category: "Documentation",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
customTags: WellKnownDiagnosticTags.CompilationEnd);
}
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,11 @@ static string GetReturnsElementText(XDocument doc)
return null;
}

if (IsXmlDocInferenceDisabledByAttribute(symbol))
{
return null;
}

var trivia = syntax.GetLeadingTrivia();
StringBuilder? builder = null;
foreach (var comment in trivia)
Expand Down Expand Up @@ -639,6 +644,47 @@ static string GetReturnsElementText(XDocument doc)
}
}

private static bool IsXmlDocInferenceDisabledByAttribute(ISymbol symbol)
{
var currentSymbol = symbol;
while (currentSymbol != null)
{
var ignoreValue = GetIgnoreValue(currentSymbol);
if (ignoreValue.HasValue)
{
return ignoreValue.Value;
}

currentSymbol = currentSymbol.ContainingSymbol;
}

var assemblyScopeIgnore = GetIgnoreValue(symbol.ContainingAssembly);
return assemblyScopeIgnore.HasValue && assemblyScopeIgnore.Value;

static bool? GetIgnoreValue(ISymbol s)
{
const string attributeName = "GraphQLIgnoreXmlDocumentationAttribute";
foreach (var attr in s.GetAttributes())
{
if (attr.AttributeClass?.Name == attributeName)
{
foreach (var arg in attr.NamedArguments)
{
if (arg is { Key: "Ignore", Value.Value: bool ignore })
{
return ignore;
}
}

// Default: Ignore = true
return true;
}
}

return null;
}
}

public static bool IsNullableType(this ITypeSymbol typeSymbol)
=> typeSymbol.IsNullableRefType() || typeSymbol.IsNullableValueType();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;

namespace HotChocolate.Types.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class XmlDocumentationEnabledAnalyzer: DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
[Errors.MissingXmlDocumentation];

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationAction(AnalyzeCompilation);
}

private static void AnalyzeCompilation(CompilationAnalysisContext context)
{
var compilation = context.Compilation;

if (IsXmlDocumentationEnabled(compilation))
{
return;
}

if (HasAssemblyIgnoreAttribute(compilation.Assembly))
{
return;
}

context.ReportDiagnostic(Diagnostic.Create(Errors.MissingXmlDocumentation, Location.None));
}

private static bool HasAssemblyIgnoreAttribute(IAssemblySymbol assembly)
{
const string attributeName = "GraphQLIgnoreXmlDocumentationAttribute";
return assembly.GetAttributes().Any(attr =>
attr.AttributeClass?.Name == attributeName
&& (attr.NamedArguments.Length == 0
|| (attr.NamedArguments[0].Key == "Ignore" && attr.NamedArguments[0].Value.Value is true)));
}

private static bool IsXmlDocumentationEnabled(Compilation compilation)
{
foreach (var tree in compilation.SyntaxTrees)
{
if (tree.Options is CSharpParseOptions csharpOptions)
{
if (csharpOptions.DocumentationMode != DocumentationMode.None)
{
return true;
}
}
}

return false;
}
}
4 changes: 4 additions & 0 deletions src/HotChocolate/Core/test/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<NoWarn>$(NoWarn);HC0106;</NoWarn>
</PropertyGroup>

<ItemGroup>
<Using Include="CookieCrumble" />
<Using Include="CookieCrumble.HotChocolate" />
Expand Down
Loading