Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add missing [Generator] attribute analyzer and code fix #5235

Closed
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
38aaa2b
Initial implementation
ryzngard Jul 9, 2021
82f5da3
I forgot to type "Attribute"
ryzngard Jul 9, 2021
261db90
Add more tests. Don't report on abstract or anonymous types
ryzngard Jul 12, 2021
d1fbafa
Add description
ryzngard Jul 12, 2021
f8f5015
PR Feedback and Cleanup
ryzngard Jul 12, 2021
8bfe76a
Formatting
ryzngard Jul 12, 2021
e9528d8
Add single quotes around Generator
ryzngard Jul 12, 2021
edeb51e
One more set of quotes...
ryzngard Jul 12, 2021
e9c4432
Update src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers…
ryzngard Jul 12, 2021
4399ef3
Generate docs
ryzngard Jul 13, 2021
51de378
Merge branch 'features/missing_generator_attribute' of https://github…
ryzngard Jul 13, 2021
e743540
Make separate code action class again
ryzngard Jul 13, 2021
27433df
Apply suggestions from code review
ryzngard Aug 12, 2021
f6defd2
Merge branch 'main' into features/missing_generator_attribute
ryzngard Aug 12, 2021
e2b0caa
Fix headers. Change to CodeAction.Create
ryzngard Aug 12, 2021
d47cb84
WIP
ryzngard Aug 12, 2021
1fc9be2
Merge branch 'main' into features/missing_generator_attribute
ryzngard Sep 3, 2021
2d5e21b
Remove getInnermostNodeForTie
ryzngard Sep 3, 2021
81fad3a
Add CultureInfo.CurrentCulture to string.Format calls
ryzngard Sep 3, 2021
7e93a8d
Merge branch 'main' into features/missing_generator_attribute
ryzngard Sep 27, 2021
e824d24
Merge branch 'main' into features/missing_generator_attribute
ryzngard Sep 29, 2021
96a23be
Fix tests, update to no longer have batch fix now that languagename m…
ryzngard Sep 29, 2021
2f43a82
do nullable right...
ryzngard Sep 29, 2021
8686834
PR feedback
ryzngard Sep 29, 2021
175bba9
More fixes that were wrong changes before. Create localized strings u…
ryzngard Sep 29, 2021
bdf1cf6
Don't pass an IFormatProvider to string.Format
ryzngard Sep 29, 2021
f1e082d
Merge branch 'main' into features/missing_generator_attribute
ryzngard Jan 14, 2022
8e52556
Merge branch 'main' into features/missing_generator_attribute
ryzngard Jan 19, 2022
5604cdc
Merge branch 'features/missing_generator_attribute' of https://github…
ryzngard Jan 19, 2022
7d63d31
Apply suggestions from code review
ryzngard Jan 19, 2022
f9c3aff
PR feedback
ryzngard Jan 19, 2022
8bd19df
Support fix all and add tests
ryzngard Jan 20, 2022
1910bc4
Merge branch 'features/missing_generator_attribute' of https://github…
ryzngard Jan 20, 2022
3a244e8
using static Microsoft.CodeAnalysis.Analyzers.CodeAnalysisDiagnostics…
ryzngard Jan 20, 2022
79d0ef2
Update md
ryzngard Jan 20, 2022
469c749
Run pack
ryzngard Jan 20, 2022
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
Expand Up @@ -5,3 +5,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
RS1034 | MicrosoftCodeAnalysisPerformance | Warning | PreferIsKindAnalyzer
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
RS1035 | MicrosoftCodeAnalysisCorrectness | Warning | SourceGeneratorAttributeAnalyzer
Original file line number Diff line number Diff line change
Expand Up @@ -529,4 +529,21 @@
<data name="PreferIsKindFix" xml:space="preserve">
<value>Use 'IsKind' instead of 'Kind'</value>
</data>
<data name="MissingSourceGeneratorAttributeDescription" xml:space="preserve">
<value>'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</value>
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="MissingSourceGeneratorAttributeMessage" xml:space="preserve">
<value>Missing 'Generator' attribute</value>
</data>
<data name="MissingSourceGeneratorAttributeTitle" xml:space="preserve">
<value>Missing 'Generator' Attribute</value>
</data>
<data name="AddGeneratorAttribute_1" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not really consistent for naming of the code fix titles but I would avoid _X.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the way to distinguish between this and AddGeneratorAttribute_2?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe _OneLanguage and _TwoLanguages? Otherwise you could use the naming style from Roslyn here.

<value>Apply Generator attribute for '{0}'.</value>
<comment>"Generator" is a named type and should not be changed</comment>
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AddGeneratorAttribute_2" xml:space="preserve">
<value>Apply Generator attribute for both '{0}' and '{1}'.</value>
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
<comment>"Generator" is a type name and should not be changed</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal static class DiagnosticIds
public const string DefineDiagnosticMessageCorrectlyRuleId = "RS1032";
public const string DefineDiagnosticDescriptionCorrectlyRuleId = "RS1033";
public const string PreferIsKindRuleId = "RS1034";
public const string MissingSourceGeneratorAttributeId = "RS1035";

// Release tracking analyzer IDs
public const string DeclareDiagnosticIdInAnalyzerReleaseRuleId = "RS2000";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;

namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.Fixers
{
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = nameof(SourceGeneratorAttributeAnalyzerFix))]
[Shared]
public sealed class SourceGeneratorAttributeAnalyzerFix : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
DiagnosticIds.MissingSourceGeneratorAttributeId);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span);

if (node is null)
{
return;
}

foreach (var diagnostic in context.Diagnostics)
{
AddFix(
string.Format(CultureInfo.CurrentCulture, CodeAnalysisDiagnosticsResources.AddGeneratorAttribute_1, LanguageNames.CSharp),
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
context, document, node, diagnostic, LanguageNames.CSharp);

AddFix(
string.Format(CultureInfo.CurrentCulture, CodeAnalysisDiagnosticsResources.AddGeneratorAttribute_1, LanguageNames.VisualBasic),
context, document, node, diagnostic, LanguageNames.VisualBasic);

AddFix(
string.Format(CultureInfo.CurrentCulture, CodeAnalysisDiagnosticsResources.AddGeneratorAttribute_2, LanguageNames.CSharp, LanguageNames.VisualBasic),
context, document, node, diagnostic, LanguageNames.CSharp, LanguageNames.VisualBasic);
}
}

private static void AddFix(string title, CodeFixContext context, Document document, SyntaxNode node, Diagnostic diagnostic, params string[] languageNames)
{
var codeAction = CodeAction.Create(
title,
(cancellationToken) => FixDocumentAsync(document, node, languageNames, cancellationToken),
equivalenceKey: nameof(SourceGeneratorAttributeAnalyzerFix));

context.RegisterCodeFix(codeAction, diagnostic);
}

public override FixAllProvider GetFixAllProvider() => null;

private static async Task<Document> FixDocumentAsync(Document document, SyntaxNode node, string[] languageNames, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var generator = editor.Generator;

SyntaxNode? generatorAttribute;

if (languageNames.Length == 1 && languageNames[0] == LanguageNames.CSharp)
{
// CSharp is the only language, which is the default paramterless
// constructor for the Generator attribute
generatorAttribute = generator.Attribute(WellKnownTypeNames.MicrosoftCodeAnalysisGeneratorAttribute);
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
// For cases where the language is VB or VB and CSharp, add
// an argument to signify that
var languageNamesFullName = typeof(LanguageNames).FullName;
var splitLanguageNames = languageNamesFullName.Split('.');

var baseLanguageNameExpression = generator.IdentifierName(splitLanguageNames[0]);
foreach (var identifier in splitLanguageNames.Skip(1))
{
baseLanguageNameExpression = generator.MemberAccessExpression(baseLanguageNameExpression, identifier);
}

var arguments = new SyntaxNode[languageNames.Length];
for (var i = 0; i < languageNames.Length; i++)
{
RoslynDebug.Assert(languageNames[i] == LanguageNames.CSharp || languageNames[i] == LanguageNames.VisualBasic);

var language = languageNames[i] == LanguageNames.CSharp
? nameof(LanguageNames.CSharp)
: nameof(LanguageNames.VisualBasic);
ryzngard marked this conversation as resolved.
Show resolved Hide resolved

var finalExpression = generator.MemberAccessExpression(baseLanguageNameExpression, language);
arguments[i] = finalExpression;
}

generatorAttribute = generator.Attribute(WellKnownTypeNames.MicrosoftCodeAnalysisGeneratorAttribute, arguments);
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
}

editor.ReplaceNode(node, generator.AddAttributes(node, generatorAttribute));

return editor.GetChangedDocument();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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.Diagnostics;

namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class SourceGeneratorAttributeAnalyzer : DiagnosticAnalyzer
{
private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.MissingSourceGeneratorAttributeTitle), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.MissingSourceGeneratorAttributeTitle), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.MissingSourceGeneratorAttributeDescription), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
ryzngard marked this conversation as resolved.
Show resolved Hide resolved

public static readonly DiagnosticDescriptor DiagnosticRule = new(
DiagnosticIds.MissingSourceGeneratorAttributeId,
s_localizableTitle,
s_localizableMessage,
DiagnosticCategory.MicrosoftCodeAnalysisCorrectness,
DiagnosticSeverity.Warning,
description: s_localizableDescription,
isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticRule);

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

context.RegisterCompilationStartAction(context =>
{
if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisISourceGenerator, out var sourceGenerator) &&
context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisGeneratorAttribute, out var generatorAttribute))
{
context.RegisterSymbolAction(c => AnalyzeSymbol(c, sourceGenerator, generatorAttribute), SymbolKind.NamedType);
}
});
}

private static void AnalyzeSymbol(SymbolAnalysisContext c, INamedTypeSymbol sourceGenerator, INamedTypeSymbol generatorAttribute)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Can we adopt the same logic from DiagnosticAnalyzerAttributeAnalyzer.AttributeAnalyzer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some things that I think are different in this case. Can you explain the benefit in the complexity that the DiagnosticAnalyzerAttributeAnalyzer.AttributeAnalyzer has? It looks like it has logic to determine what languages are supported based on referenced assemblies.

{
var symbol = (INamedTypeSymbol)c.Symbol;

if (symbol.IsAbstract || symbol.IsAnonymousType)
{
return;
}

if (!symbol.AllInterfaces.Contains(sourceGenerator))
{
return;
}

if (symbol.GetApplicableAttributes(null).Any(a => a.AttributeClass.Equals(generatorAttribute, SymbolEqualityComparer.Default)))
{
return;
}

c.ReportDiagnostic(symbol.CreateDiagnostic(DiagnosticRule, symbol.Name));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
<target state="translated">Přidat položku pravidla k nevydanému souboru verze</target>
<note />
</trans-unit>
<trans-unit id="AddGeneratorAttribute_1">
<source>Apply Generator attribute for '{0}'.</source>
<target state="new">Apply Generator attribute for '{0}'.</target>
<note>"Generator" is a named type and should not be changed</note>
</trans-unit>
<trans-unit id="AddGeneratorAttribute_2">
<source>Apply Generator attribute for both '{0}' and '{1}'.</source>
<target state="new">Apply Generator attribute for both '{0}' and '{1}'.</target>
<note>"Generator" is a type name and should not be changed</note>
</trans-unit>
<trans-unit id="ClassIsNotDiagnosticAnalyzerMessage">
<source>Inherit type '{0}' from DiagnosticAnalyzer or remove the DiagnosticAnalyzerAttribute(s)</source>
<target state="translated">Zděďte typ {0} z DiagnosticAnalyzer, nebo odeberte atributy DiagnosticAnalyzerAttribute.</target>
Expand Down Expand Up @@ -232,6 +242,21 @@
<target state="translated">Použijte atribut DiagnosticAnalyzer jak pro {0}, tak i pro {1}.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeDescription">
<source>'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</source>
<target state="new">'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeMessage">
<source>Missing 'Generator' attribute</source>
<target state="new">Missing 'Generator' attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeTitle">
<source>Missing 'Generator' Attribute</source>
<target state="new">Missing 'Generator' Attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSymbolKindArgumentToRegisterActionMessage">
<source>Specify at least one SymbolKind of interest when registering a symbol analyzer action</source>
<target state="translated">Při registraci akce analyzátoru symbolů zadejte minimálně jeden požadovaný SymbolKind.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
<target state="translated">Regeleintrag der nicht veröffentlichten Releasedatei hinzufügen</target>
<note />
</trans-unit>
<trans-unit id="AddGeneratorAttribute_1">
<source>Apply Generator attribute for '{0}'.</source>
<target state="new">Apply Generator attribute for '{0}'.</target>
<note>"Generator" is a named type and should not be changed</note>
</trans-unit>
<trans-unit id="AddGeneratorAttribute_2">
<source>Apply Generator attribute for both '{0}' and '{1}'.</source>
<target state="new">Apply Generator attribute for both '{0}' and '{1}'.</target>
<note>"Generator" is a type name and should not be changed</note>
</trans-unit>
<trans-unit id="ClassIsNotDiagnosticAnalyzerMessage">
<source>Inherit type '{0}' from DiagnosticAnalyzer or remove the DiagnosticAnalyzerAttribute(s)</source>
<target state="translated">Übernehmen Sie den Typ "{0}" von DiagnosticAnalyzer, oder entfernen Sie DiagnosticAnalyzerAttribute(s).</target>
Expand Down Expand Up @@ -232,6 +242,21 @@
<target state="translated">Wenden Sie das DiagnosticAnalyzer-Attribut sowohl für "{0}" als auch für "{1}" an.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeDescription">
<source>'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</source>
<target state="new">'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeMessage">
<source>Missing 'Generator' attribute</source>
<target state="new">Missing 'Generator' attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeTitle">
<source>Missing 'Generator' Attribute</source>
<target state="new">Missing 'Generator' Attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSymbolKindArgumentToRegisterActionMessage">
<source>Specify at least one SymbolKind of interest when registering a symbol analyzer action</source>
<target state="translated">Geben Sie bei der Registrierung einer Symbolanalyseaktion mindestens einen relevanten SymbolKind-Wert an.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
<target state="translated">Agregar una entrada de regla a un archivo de versión no incluido</target>
<note />
</trans-unit>
<trans-unit id="AddGeneratorAttribute_1">
<source>Apply Generator attribute for '{0}'.</source>
<target state="new">Apply Generator attribute for '{0}'.</target>
<note>"Generator" is a named type and should not be changed</note>
</trans-unit>
<trans-unit id="AddGeneratorAttribute_2">
<source>Apply Generator attribute for both '{0}' and '{1}'.</source>
<target state="new">Apply Generator attribute for both '{0}' and '{1}'.</target>
<note>"Generator" is a type name and should not be changed</note>
</trans-unit>
<trans-unit id="ClassIsNotDiagnosticAnalyzerMessage">
<source>Inherit type '{0}' from DiagnosticAnalyzer or remove the DiagnosticAnalyzerAttribute(s)</source>
<target state="translated">Herede el tipo "{0}" de DiagnosticAnalyzer o quite los elementos DiagnosticAnalyzerAttribute.</target>
Expand Down Expand Up @@ -232,6 +242,21 @@
<target state="translated">Aplique el atributo DiagnosticAnalyzer para “{0}” y “{1}”.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeDescription">
<source>'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</source>
<target state="new">'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeMessage">
<source>Missing 'Generator' attribute</source>
<target state="new">Missing 'Generator' attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeTitle">
<source>Missing 'Generator' Attribute</source>
<target state="new">Missing 'Generator' Attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSymbolKindArgumentToRegisterActionMessage">
<source>Specify at least one SymbolKind of interest when registering a symbol analyzer action</source>
<target state="translated">Especifique al menos un elemento SymbolKind de interés al registrar una acción del analizador de símbolos.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
<target state="translated">Ajouter une entrée de règle au fichier de version non fourni</target>
<note />
</trans-unit>
<trans-unit id="AddGeneratorAttribute_1">
<source>Apply Generator attribute for '{0}'.</source>
<target state="new">Apply Generator attribute for '{0}'.</target>
<note>"Generator" is a named type and should not be changed</note>
</trans-unit>
<trans-unit id="AddGeneratorAttribute_2">
<source>Apply Generator attribute for both '{0}' and '{1}'.</source>
<target state="new">Apply Generator attribute for both '{0}' and '{1}'.</target>
<note>"Generator" is a type name and should not be changed</note>
</trans-unit>
<trans-unit id="ClassIsNotDiagnosticAnalyzerMessage">
<source>Inherit type '{0}' from DiagnosticAnalyzer or remove the DiagnosticAnalyzerAttribute(s)</source>
<target state="translated">Effectuez un héritage de type '{0}' à partir de DiagnosticAnalyzer, ou supprimez les instances de DiagnosticAnalyzerAttribute</target>
Expand Down Expand Up @@ -232,6 +242,21 @@
<target state="translated">Appliquez l'attribut DiagnosticAnalyzer pour '{0}' et '{1}'.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeDescription">
<source>'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</source>
<target state="new">'{0}' implements 'ISourceGenerator' but does not have the 'Generator' attribute.</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeMessage">
<source>Missing 'Generator' attribute</source>
<target state="new">Missing 'Generator' attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSourceGeneratorAttributeTitle">
<source>Missing 'Generator' Attribute</source>
<target state="new">Missing 'Generator' Attribute</target>
<note />
</trans-unit>
<trans-unit id="MissingSymbolKindArgumentToRegisterActionMessage">
<source>Specify at least one SymbolKind of interest when registering a symbol analyzer action</source>
<target state="translated">Spécifiez au moins un SymbolKind d'intérêt au moment d'inscrire une action d'analyseur de symbole</target>
Expand Down
Loading