Avisos do compilador personalizado

115

Ao usar o ObsoleteAtribute em .Net, ele fornece avisos do compilador informando que o objeto / método / propriedade está obsoleto e que algo mais deve ser usado. Atualmente estou trabalhando em um projeto que requer muita refatoração de um código de ex-funcionários. Quero escrever um atributo personalizado que posso usar para marcar métodos ou propriedades que irão gerar avisos do compilador que fornecem mensagens que escrevo. Algo assim

[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

Eu quero que isso gere um aviso do compilador que diz: "Este código sux e deve ser examinado". Eu sei como criar um atributo personalizado, a questão é como faço para gerar avisos do compilador no visual studio.

Micah
fonte
Este é C #? Vou retag presumivelmente como C # (não C), presumindo que é isso que o autor do pôster original quis escolher.
Onorio Catenacci
13
Isso não é válido VB ou C # ... então o que é ...?!
ljs
5
Pergunta antiga, mas você pode definir avisos do compilador personalizado usando Roslyn agora.
RJ Cuthbertson
4
@jrummell Na língua de Roslyn, analisadores de código: johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson
2
@RJCuthbertson Mudei seu comentário para a resposta aceita para dar-lhe a atenção que merece.
jpaugh

Respostas:

27

Atualizar

Agora isso é possível com Roslyn (Visual Studio 2015). Você pode construir um analisador de código para verificar se há um atributo personalizado


Não acredito que seja possível. ObsoleteAttribute é tratado de forma especial pelo compilador e é definido no padrão C #. Por que diabos o ObsoleteAttribute não é aceitável? Parece-me que essa é exatamente a situação para a qual foi projetada e atinge exatamente o que você precisa!

Observe também que o Visual Studio capta os avisos gerados pelo ObsoleteAttribute instantaneamente, o que é muito útil.

Não pretendo ser inútil, apenas me perguntando por que você não gosta de usá-lo ...

Infelizmente ObsoleteAttribute é lacrado (provavelmente em parte devido ao tratamento especial), portanto, você não pode criar subclasses de seu próprio atributo a partir dele.

Do padrão C #: -

O atributo Obsolete é usado para marcar tipos e membros de tipos que não devem mais ser usados.

Se um programa usar um tipo ou membro decorado com o atributo Obsolete, o compilador emitirá um aviso ou um erro. Especificamente, o compilador emite um aviso se nenhum parâmetro de erro for fornecido ou se o parâmetro de erro for fornecido e tiver o valor false. O compilador emitirá um erro se o parâmetro de erro for especificado e tiver o valor true.

Isso não resume as suas necessidades? ... Você não vai fazer melhor do que isso, eu não acho.

ljs
fonte
14
Estou procurando a mesma coisa. Obsoleto 'funciona', mas o código não é realmente obsoleto, e sim incompleto devido à refatoração.
g.
11
Eu concordo com @g, e provavelmente com o autor original. Obsoleto significa obsoleto, não use. Eu quero sinalizar algo como "ei, isso compila, mas realmente precisamos a) completar a funcionalidade ou b) refatorar". Seria mais um atributo de tempo de desenvolvimento. Além disso, as tarefas funcionam, por exemplo, // TODO :, mas eu não as uso, como estou supondo que muitas pessoas não, mas revise os avisos do compilador regularmente.
MikeJansen
8
Outro motivo para não usar a [Obsolete]tag é que pode causar problemas se você precisar fazer XmlSerialization com a propriedade. Adicionar a [Obsolete]tag também adiciona [XmlIgnore]atributo nos bastidores.
burnttoast11
6
Obsoleto é diferente. Obsolete lhe dará um aviso em cada linha de código que chamar esse método. Não acho que seja isso o que o autor da postagem deseja (pelo menos não é o que eu queria quando fiz uma pesquisa e encontrei essa pergunta). Pensei que o que a pergunta buscava era que aparecesse um aviso na definição da função, nunca no lugar em que ela está sendo usada.
Nick
Não é a melhor resposta. -1 por pensar que sua incapacidade de inventar uma razão para não usá-lo merece crítica. Essa atitude desencoraja a autenticidade.
Mike Socha III,
96

Vale a pena tentar.

Você não pode estender Obsolete, porque é final, mas talvez você possa criar seu próprio atributo e marcar essa classe como obsoleta assim:

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

Então, quando você marca seus métodos com o atributo "MustRefactor", os avisos de compilação serão exibidos. Ele gera um aviso em tempo de compilação, mas a mensagem de erro parece engraçada, você deve ver por si mesmo e escolher. Isso está muito próximo do que você queria alcançar.

ATUALIZAÇÃO: Com esse código gera um aviso (não muito legal, mas acho que não tem algo melhor).

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
fonte
Você pode colar o que ele gera? Estou curioso.
Micah
1
O aviso de compilação é disparado mesmo se a propriedade / método não for chamada.
Rolf Kristensen de
1
Boas sugestões aqui. Eu estava tentando fazer a mesma coisa e acabei lançando NotImplementedExceptions. Não é a melhor solução, pois eles não aparecem em tempo de compilação, apenas em tempo de execução se o código for executado. Vou tentar fazer isso sozinho.
MonkeyWrench
1
Não seria ótimo se o ObsolteAttribute pudesse suportar expressões como DebuggerDisplayAttribute, então poderíamos realmente fazer algumas coisas legais. visualstudio.uservoice.com/forums/121579-visual-studio/…
jpierson
Se você implementar IDisposablenessas classes obsoletas, significa que pode embrulhar seu código de teste duvidoso em um usingbloco. Como esta: using(new MustRefactor()){DodgyCode();}. Então você pode encontrar todos os usos quando terminar. Estou usando isso agora para Sleepo thread dentro de um loop for que preciso desacelerar artificialmente para fins de depuração.
Iain Fraser
48

Em alguns compiladores, você pode usar #warning para emitir um aviso:

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

Em compiladores Microsoft, você normalmente pode usar o pragma da mensagem:

#pragma message ( "text" )

Você mencionou .Net, mas não especificou se estava programando em C / C ++ ou C #. Se você estiver programando em C #, saiba que C # oferece suporte ao formato #warning .

Douglas Mayle
fonte
1
#warning ou #pragma são diretivas de pré-processador e, portanto, serão executados independentemente da presença de qualquer código de ex-colegas de micah e não interagem com o atributo de forma alguma.
Certamente que
39

No momento, estamos no meio de muita refatoração, onde não poderíamos consertar tudo imediatamente. Nós apenas usamos o comando #warning preproc onde precisamos voltar e olhar o código. Ele aparece na saída do compilador. Não acho que você pode colocá-lo em um método, mas você poderia colocá-lo apenas dentro do método e ainda é fácil de encontrar.

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

No VS 2008 (+ sp1) #warnings não são exibidos corretamente na lista de erros após a solução Clean Soultion & Rebuild, nem todos eles. Alguns avisos são mostrados na lista de erros somente depois que eu abrir um arquivo de classe particular. Então fui forçado a usar o atributo personalizado:

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

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

    public MappingToDo()
    {}
}

Então, quando sinalizo algum código com ele

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

Ele produz avisos como este:

Namespace.MappingToDo é obsoleto: 'Mapping ToDo'.

Não consigo alterar o texto do aviso, 'Algum comentário' não aparece na Lista de Erros. Mas ele irá pular para o lugar apropriado no arquivo. Portanto, se você precisar variar essas mensagens de aviso, crie vários atributos.

Tomasz Modelski
fonte
6

O que você está tentando fazer é mau uso dos atributos. Em vez disso, use a Lista de Tarefas do Visual Studio. Você pode inserir um comentário em seu código como este:

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

Em seguida, abra Exibir / Lista de Tarefas no menu. A lista de tarefas tem duas categorias, tarefas do usuário e comentários. Mude para Comentários e você verá todos os seus // Todo: lá. Clicar duas vezes em um TODO irá direcionar para o comentário em seu código.

Al

user4089256
fonte
1
Acho esta solução mais preferível
Samuel
1
e se você quiser marcar uma função como "Não deve ser chamada no código de produção" ou semelhante. Portanto, você deseja que seja acionado se uma função ou classe for chamada ou instanciada, mas não se for apenas compilada.
Jesse Pepper
2

Eu não acho que você pode. Até onde eu sei, o suporte para ObsoleteAttribute é essencialmente codificado no compilador C #; você não pode fazer nada semelhante diretamente.

O que você pode fazer é usar uma tarefa MSBuild (ou um evento pós-compilação) que executa uma ferramenta personalizada no assembly recém-compilado. A ferramenta personalizada refletiria sobre todos os tipos / métodos na montagem e consumiria seu atributo personalizado, ponto em que poderia imprimir no TextWriters padrão ou de erro do System.Console.

tecnófilo
fonte
2

Olhando para o código-fonte do ObsoleteAttribute , não parece que está fazendo nada de especial para gerar um aviso do compilador, então eu tenderia a ir com @ technophile e dizer que ele está embutido no compilador. Existe um motivo pelo qual você não deseja apenas usar ObsoleteAttribute para gerar suas mensagens de aviso?

bdukes
fonte
Nenhum motivo específico, exceto o código, não é necessariamente obsoleto.
Micah
1
Ele está especificado na especificação C # como sendo tratado especialmente pelo compilador, verifique minha resposta :-). Micah - 'O atributo Obsoleto é usado para marcar tipos e membros de tipos que não devem mais ser usados.' da especificação. Isso não é aplicável? ...
ljs
Apenas se alguém quisesse saber, não existe código C # no código-fonte para fazer isso. referênciasource.microsoft.com
mscorlib
1

Existem vários comentários que sugerem a inserção de advertências ou pragma. Obsolete funciona de uma maneira muito diferente! Marcando como obsoleta uma função de uma biblioteca L, a mensagem obsoleta surge quando um programa chama a função, mesmo se o programa chamador não estiver na biblioteca L. Aviso levanta a mensagem SOMENTE quando L é compilado.

bubi
fonte
1

Aqui está a implementação do Roslyn, para que você possa criar seus próprios atributos que fornecem avisos ou erros imediatamente.

Eu criei um atributo Type Called IdeMessageque será o atributo que gera avisos:

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

    public IDEMessageAttribute(string message);
}

Para fazer isso, você precisa instalar o Roslyn SDK primeiro e iniciar um novo projeto VSIX com analisador. Omiti algumas das partes menos relevantes, como as mensagens, você pode descobrir como fazer isso. Em seu analisador, você faz isso

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);
}

Não há CodeFixProvider para isso, você pode removê-lo da solução.

johnny 5
fonte