Avertissements personnalisés du compilateur

115

Lorsque vous utilisez ObsoleteAtribute dans .Net, il vous donne des avertissements du compilateur vous indiquant que l'objet / la méthode / la propriété est obsolète et que quelque chose d'autre doit être utilisé. Je travaille actuellement sur un projet qui nécessite beaucoup de refactorisation d'un code d'anciens employés. Je veux écrire un attribut personnalisé que je peux utiliser pour marquer des méthodes ou des propriétés qui généreront des avertissements du compilateur qui donneront des messages que j'écris. Quelque chose comme ça

[MyAttribute("This code sux and should be looked at")]
public void DoEverything()
{
}
<MyAttribute("This code sux and should be looked at")>
Public Sub DoEverything()
End Sub

Je veux que cela génère un avertissement du compilateur qui dit, "Ce code sux et devrait être examiné". Je sais comment créer un attribut personnalisé, la question est de savoir comment le faire générer des avertissements de compilateur dans Visual Studio.

Michée
la source
Est-ce C #? Je vais vraisemblablement repenser cela en C # (et non en C) sur la présomption que c'est ce que l'affiche originale voulait choisir.
Onorio Catenacci
13
Ce n'est pas valide VB ou C # ... alors qu'est-ce que c'est ...?!
ljs
5
Ancienne question, mais vous pouvez maintenant définir des avertissements de compilateur personnalisés à l'aide de Roslyn.
RJ Cuthbertson
4
@jrummell Dans Roslyn parler, analyseurs de code: johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson
2
@RJCuthbertson J'ai déplacé votre commentaire dans la réponse acceptée pour lui donner l'attention qu'elle mérite.
jpaugh

Réponses:

27

Mettre à jour

C'est désormais possible avec Roslyn (Visual Studio 2015). Vous pouvez créer un analyseur de code pour rechercher un attribut personnalisé


Je ne crois pas que ce soit possible. ObsoleteAttribute est traité spécialement par le compilateur et est défini dans la norme C #. Pourquoi diable est ObsoleteAttribute inacceptable? Il me semble que c'est précisément la situation pour laquelle il a été conçu et réalise exactement ce dont vous avez besoin!

Notez également que Visual Studio récupère les avertissements générés par ObsoleteAttribute à la volée également, ce qui est très utile.

Ne voulez pas être inutile, vous demandez simplement pourquoi vous n'aimez pas l'utiliser ...

Malheureusement, ObsoleteAttribute est scellé (probablement en partie à cause du traitement spécial), vous ne pouvez donc pas en sous-classer votre propre attribut.

De la norme C #: -

L'attribut Obsolète est utilisé pour marquer les types et les membres des types qui ne doivent plus être utilisés.

Si un programme utilise un type ou un membre décoré avec l'attribut Obsolète, le compilateur émet un avertissement ou une erreur. Plus précisément, le compilateur émet un avertissement si aucun paramètre d'erreur n'est fourni ou si le paramètre d'erreur est fourni et a la valeur false. Le compilateur émet une erreur si le paramètre d'erreur est spécifié et a la valeur true.

Cela ne résume-t-il pas vos besoins? ... vous n'allez pas faire mieux que cela, je ne pense pas.

ljs
la source
14
Je cherche la même chose. Obsolète «fonctionne» mais le code n'est pas vraiment obsolète mais incomplet en raison de la refactorisation.
g.
11
Je suis d'accord avec @g, et probablement l'auteur original. Obsolète signifie obsolète, ne pas utiliser. Je veux marquer quelque chose comme "hé cela compile mais nous devons vraiment a) compléter la fonctionnalité ou b) refactoriser". Ce serait plutôt un attribut de temps de développement. Les tâches fonctionnent également, par exemple // TODO :, mais je ne les utilise pas, car je suppose que beaucoup de gens ne le font pas, mais revoyez régulièrement les avertissements du compilateur.
MikeJansen
8
Une autre raison de ne pas utiliser la [Obsolete]balise est que cela pourrait poser des problèmes si vous devez effectuer XmlSerialization avec la propriété. L'ajout de la [Obsolete]balise ajoute également un [XmlIgnore]attribut dans les coulisses.
burnttoast11
6
Obsolète est différent. Obsolète vous donnera un avertissement sur chaque ligne de code qui appelle cette méthode. Je ne pense pas que ce soit ce que veut l'affiche (du moins ce n'est pas ce que je veux quand j'ai fait une recherche et trouvé cette question). Je pensais que ce que la question cherchait était un avertissement à apparaître sur la définition de la fonction, pas jamais à l'endroit où elle est utilisée.
Nick
Pas la meilleure réponse. -1 pour avoir pensé que votre incapacité à trouver une raison pour ne pas l'utiliser mérite d'être critiquée. Cette attitude décourage l'authenticité.
Mike Socha III
96

Cela vaut la peine d'essayer.

Vous ne pouvez pas étendre Obsolète, car c'est définitif, mais peut-être pouvez-vous créer votre propre attribut et marquer cette classe comme obsolète comme ceci:

[Obsolete("Should be refactored")]
public class MustRefactor: System.Attribute{}

Ensuite, lorsque vous marquez vos méthodes avec l'attribut "MustRefactor", les avertissements de compilation s'afficheront. Il génère un avertissement lors de la compilation, mais le message d'erreur semble drôle, vous devriez le voir par vous-même et choisir. C'est très proche de ce que vous vouliez réaliser.

MISE À JOUR: Avec ce code, cela génère un avertissement (pas très gentil, mais je ne pense pas qu'il y ait quelque chose de mieux).

public class User
{
    private String userName;

    [TooManyArgs] // Will show warning: Try removing some arguments
    public User(String userName)
    {
        this.userName = userName;   
    }

    public String UserName
    {
        get { return userName; }
    }
    [MustRefactor] // will show warning: Refactor is needed Here
    public override string ToString()
    {
        return "User: " + userName;
    }
}
[Obsolete("Refactor is needed Here")]
public class MustRefactor : System.Attribute
{

}
[Obsolete("Try removing some arguments")]
public class TooManyArgs : System.Attribute
{

}
Pablo Fernandez
la source
Pouvez-vous coller ce qu'il génère? Je suis curieux.
Micah
1
L'avertissement de compilation est déclenché même si la propriété / méthode n'est pas appelée.
Rolf Kristensen
1
Bonnes suggestions ici. Je cherchais à faire la même chose et j'ai fini par lancer des exceptions NotImplementedExceptions. Ce n'est pas la meilleure solution car ils n'apparaissent pas au moment de la compilation, uniquement au moment de l'exécution si le code est exécuté. Je vais essayer moi-même.
MonkeyWrench
1
Ne serait-ce pas génial si ObsolteAttribute pouvait prendre en charge des expressions comme DebuggerDisplayAttribute, alors nous pourrions vraiment faire des choses intéressantes. visualstudio.uservoice.com/forums/121579-visual-studio/…
jpierson
Si vous implémentez IDisposablesur ces classes obsolètes, cela signifie que vous pouvez envelopper votre code de test douteux dans un usingbloc. Comme ceci: using(new MustRefactor()){DodgyCode();}. Ensuite, vous pouvez trouver tous les usages lorsque vous avez terminé. J'utilise ceci en ce moment pour Sleeple thread dans une boucle for dont j'ai besoin pour ralentir artificiellement à des fins de débogage.
Iain Fraser
48

Dans certains compilateurs, vous pouvez utiliser #warning pour émettre un avertissement:

#warning "Do not use ABC, which is deprecated. Use XYZ instead."

Dans les compilateurs Microsoft, vous pouvez généralement utiliser le pragma de message:

#pragma message ( "text" )

Vous avez mentionné .Net, mais vous n'avez pas précisé si vous programmiez avec C / C ++ ou C #. Si vous programmez en C #, sachez que C # prend en charge le format #warning .

Douglas Mayle
la source
1
#warning ou #pragma sont des directives de pré-processeur et donc fonctionneront indépendamment de la présence de n'importe quel code des ex-collègues de micah, et il n'interagit pas du tout avec l'attribut. Assez certain Obsolète est le seul moyen d'y parvenir ...
ljs
39

Nous sommes actuellement en plein refactoring où nous ne pouvions pas tout réparer tout de suite. Nous utilisons simplement la commande #warning preproc où nous devons revenir en arrière et regarder le code. Il apparaît dans la sortie du compilateur. Je ne pense pas que vous puissiez le mettre sur une méthode, mais vous pouvez le mettre juste à l'intérieur de la méthode, et c'est toujours facile à trouver.

public void DoEverything() {
   #warning "This code sucks"
}
Ted Elliott
la source
7

Dans VS 2008 (+ sp1) les #warnings ne s'affichent pas correctement dans la liste des erreurs après Clean Soultion & Rebuild Solution, pas tous. Certains avertissements ne sont affichés dans la liste d'erreurs qu'après l'ouverture d'un fichier de classe particulier. J'ai donc été obligé d'utiliser un attribut personnalisé:

[Obsolete("Mapping ToDo")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class MappingToDo : System.Attribute
{
    public string Comment = "";

    public MappingToDo(string comment)
    {
        Comment = comment;
    }

    public MappingToDo()
    {}
}

Alors quand je signale du code avec

[MappingToDo("Some comment")]
public class MembershipHour : Entity
{
    // .....
}

Il produit des avertissements comme celui-ci:

Namespace.MappingToDo est obsolète: «Mapping ToDo».

Je ne peux pas modifier le texte de l'avertissement, "Certains commentaires" ne sont pas affichés Liste des erreurs. Mais il sautera à la bonne place dans le fichier. Donc, si vous avez besoin de modifier ces messages d'avertissement, créez divers attributs.

Tomasz Modelski
la source
6

Ce que vous essayez de faire, c'est une mauvaise utilisation des attributs. Utilisez plutôt la liste des tâches de Visual Studio. Vous pouvez entrer un commentaire dans votre code comme ceci:

//TODO:  This code sux and should be looked at
public class SuckyClass(){
  //TODO:  Do something really sucky here!
}

Ensuite, ouvrez Affichage / Liste des tâches dans le menu. La liste des tâches comprend deux catégories, les tâches utilisateur et les commentaires. Passez à Commentaires et vous verrez tous vos // Todo: est là. Double-cliquez sur un TODO pour accéder au commentaire dans votre code.

Al

user4089256
la source
1
Je trouve que c'est une solution plus préférable
Samuel
1
Que faire si vous souhaitez marquer une fonction comme "Ne pas appeler dans le code de production" ou similaire. Vous voulez donc qu'il se déclenche si une fonction ou une classe est appelée ou instanciée, mais pas si elle vient d'être compilée.
Jesse Pepper
2

Je ne pense pas que vous puissiez. Autant que je sache, la prise en charge d'ObsoleteAttribute est essentiellement codée en dur dans le compilateur C #; vous ne pouvez rien faire de similaire directement.

Vous pouvez peut-être utiliser une tâche MSBuild (ou un événement post-build) qui exécute un outil personnalisé par rapport à l'assembly qui vient d'être compilé. L'outil personnalisé refléterait tous les types / méthodes de l'assembly et consommerait votre attribut personnalisé, auquel cas il pourrait imprimer sur les TextWriters par défaut ou d'erreur de System.Console.

technophile
la source
2

En regardant la source pour ObsoleteAttribute , il ne semble pas que cela fasse quelque chose de spécial pour générer un avertissement du compilateur, donc j'aurais tendance à aller avec @ technophile et à dire qu'il est codé en dur dans le compilateur. Y a-t-il une raison pour laquelle vous ne souhaitez pas simplement utiliser ObsoleteAttribute pour générer vos messages d'avertissement?

bdukes
la source
Aucune raison particulière autre que le code n'est nécessairement obsolète.
Micah
1
Il est spécifié dans la spécification C # comme étant traité spécialement par le compilateur, consultez ma réponse :-). Micah - 'L'attribut Obsolète est utilisé pour marquer les types et les membres de types qui ne devraient plus être utilisés.' de la spécification. N'est-ce pas applicable? ...
ljs
Juste si quelqu'un se demandait, il n'y a pas non plus de code C # dans le code source pour faire cela. referencesource.microsoft.com/#mscorlib/system/…
Paweł Mach
1

Il y a plusieurs commentaires qui suggèrent d'insérer des avertissements ou un pragma. Obsolète fonctionne d'une manière très différente! Marquant une fonction d'une bibliothèque L comme obsolète, le message obsolète se déclenche lorsqu'un programme appelle la fonction même si le programme appelant n'est pas dans la bibliothèque L. Avertissement déclenche le message UNIQUEMENT lorsque L est compilé.

bubi
la source
1

Voici l'implémentation Roslyn, vous pouvez donc créer vos propres attributs qui donnent des avertissements ou des erreurs à la volée.

J'ai créé un attribut Type Called IdeMessagequi sera l'attribut qui génère des avertissements:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class IDEMessageAttribute : Attribute
{
    public string Message;

    public IDEMessageAttribute(string message);
}

Pour ce faire, vous devez d'abord installer le SDK Roslyn et démarrer un nouveau projet VSIX avec l'analyseur. J'ai omis certains des éléments les moins pertinents comme les messages, vous pouvez trouver comment le faire. Dans votre analyseur, vous faites cela

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(AnalyzerInvocation, SyntaxKind.InvocationExpression);
}

private static void AnalyzerInvocation(SyntaxNodeAnalysisContext context)
{
    var invocation = (InvocationExpressionSyntax)context.Node;

    var methodDeclaration = (context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken).Symbol as IMethodSymbol);

    //There are several reason why this may be null e.g invoking a delegate
    if (null == methodDeclaration)
    {
        return;
    }

    var methodAttributes = methodDeclaration.GetAttributes();
    var attributeData = methodAttributes.FirstOrDefault(attr => IsIDEMessageAttribute(context.SemanticModel, attr, typeof(IDEMessageAttribute)));
    if(null == attributeData)
    {
        return;
    }

    var message = GetMessage(attributeData); 
    var diagnostic = Diagnostic.Create(Rule, invocation.GetLocation(), methodDeclaration.Name, message);
    context.ReportDiagnostic(diagnostic);
}

static bool IsIDEMessageAttribute(SemanticModel semanticModel, AttributeData attribute, Type desiredAttributeType)
{
    var desiredTypeNamedSymbol = semanticModel.Compilation.GetTypeByMetadataName(desiredAttributeType.FullName);

    var result = attribute.AttributeClass.Equals(desiredTypeNamedSymbol);
    return result;
}

static string GetMessage(AttributeData attribute)
{
    if (attribute.ConstructorArguments.Length < 1)
    {
        return "This method is obsolete";
    }

    return (attribute.ConstructorArguments[0].Value as string);
}

Il n'y a pas de CodeFixProvider pour cela, vous pouvez le supprimer de la solution.

johnny 5
la source