2012-03-19 10 views
11

Tengo un conjunto de herramientas que tiene muchos métodos que a menudo toman Expression<Func<T,TProperty>> como parámetros. Algunos pueden ser de un solo nivel (o=>o.Name), mientras que otros pueden ser de varios niveles (o=>o.EmployeeData.Address.Street).¿Roslyn es la herramienta adecuada para una comprobación de expresiones en tiempo de compilación?

Quiero desarrollar algo (MSBuild Task? Visual Studio Plugin? Con suerte el primero) que lee todos los archivos .cs del usuario y genera errores de compilación si el parámetro no es una expresión de propiedad (pero algo así como o=>o.Contains("foo")) , o si se da una expresión de niveles múltiples donde solo se permite un nivel único.

he intentado mirar el código IL compilado principio, pero ya que los árboles de expresión son un compilador de C# "truco", en la IL todo lo que veo es la creación de instancias de expresión y tal, y mientras podía verificación de cada uno si sólo MemberExpressions (y el número correcto de ellos) se crean, no es tan bueno.

Entonces Roslyn vino a mi mente. ¿Es posible escribir algo como esto con Roslyn?

+0

¿Por qué necesita hacer cumplir estas limitaciones? –

+0

porque las cosas que hago en estos métodos (gestión de cambio de propiedad, comprobación de errores, etc.) solo tienen sentido en las expresiones de propiedad – TDaver

+3

y porque esto se sintió como algo interesante de hacer :) – TDaver

Respuesta

11

Sí, creo que Roslyn y sus problemas de código son exactamente la herramienta adecuada para esto. Con ellos, puede analizar el código mientras escribe y crea errores (o advertencias) que se muestran como otros errores en Visual Studio.

He tratado de crear dicha emisión código:

[ExportSyntaxNodeCodeIssueProvider("PropertyExpressionCodeIssue", LanguageNames.CSharp, typeof(InvocationExpressionSyntax))] 
class PropertyExpressionCodeIssueProvider : ICodeIssueProvider 
{ 
    [ImportingConstructor] 
    public PropertyExpressionCodeIssueProvider() 
    {} 

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken) 
    { 
     var invocation = (InvocationExpressionSyntax)node; 

     var semanticModel = document.GetSemanticModel(cancellationToken); 

     var semanticInfo = semanticModel.GetSemanticInfo(invocation, cancellationToken); 

     var methodSymbol = (MethodSymbol)semanticInfo.Symbol; 

     if (methodSymbol == null) 
      yield break; 

     var attributes = methodSymbol.GetAttributes(); 

     if (!attributes.Any(a => a.AttributeClass.Name == "PropertyExpressionAttribute")) 
      yield break; 

     var arguments = invocation.ArgumentList.Arguments; 
     foreach (var argument in arguments) 
     { 
      var lambdaExpression = argument.Expression as SimpleLambdaExpressionSyntax; 
      if (lambdaExpression == null) 
       continue; 

      var parameter = lambdaExpression.Parameter; 
      var memberAccess = lambdaExpression.Body as MemberAccessExpressionSyntax; 
      if (memberAccess != null) 
      { 
       var objectIdentifierSyntax = memberAccess.Expression as IdentifierNameSyntax; 

       if (objectIdentifierSyntax != null 
        && objectIdentifierSyntax.PlainName == parameter.Identifier.ValueText 
        && semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol is PropertySymbol) 
        continue; 
      } 

      yield return 
       new CodeIssue(
        CodeIssue.Severity.Error, argument.Span, 
        string.Format("Has to be simple property access of '{0}'", parameter.Identifier.ValueText)); 
     } 
    } 

    #region Unimplemented ICodeIssueProvider members 

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken) 
    { 
     throw new NotImplementedException(); 
    } 

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxTrivia trivia, CancellationToken cancellationToken) 
    { 
     throw new NotImplementedException(); 
    } 

    #endregion 
} 

El uso sería como esto:

[AttributeUsage(AttributeTargets.Method)] 
class PropertyExpressionAttribute : Attribute 
{ } 

… 

[PropertyExpression] 
static void Foo<T>(Expression<Func<SomeType, T>> expr) 
{ } 

… 

Foo(x => x.P); // OK 
Foo(x => x.M()); // error 
Foo(x => 42); // error 

El código anterior tiene varios problemas:

  1. Es completamente sin optimizar .
  2. Es probable que necesite un poco más de comprobación de errores.
  3. No funciona. Al menos en el CTP actual. La expresión semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol cerca del final siempre devuelve null. Esto se debe a que la semántica de los árboles de expresiones se encuentra entre the currently unimplemented features.
+3

¿acabas de escribir mi tarea? : D – TDaver

+0

Roslyn salió con un nuevo CTP. ¿Supongo que sería un buen momento para probar esto otra vez? :) – TDaver

5

Sí, es totalmente posible. El problema es que Roslyn aún no admite todas las construcciones de lenguaje, por lo que es posible que te topes con algunas cosas no compatibles. Los árboles de expresión no se admiten porque Roslyn no puede compilar código que genere expresiones, pero debe poder llegar lo suficientemente lejos como para que algunas cosas funcionen.

En un nivel alto, si desea implementar esto como una tarea MSBuild, en su tarea de compilación puede llamar al Roslyn.Services.Workspace.LoadSolution o Roslyn.Services.Workspace.LoadStandaloneProject. Luego recorrería los árboles de sintaxis buscando menciones de sus diversos métodos, y luego los vincularía para asegurarse de que en realidad es el método que cree que está llamando. A partir de ahí, puede encontrar los nodos de sintaxis lambda y realizar cualquier análisis sintáctico/semántico que desee desde allí.

Existen algunos ejemplos de proyectos en el CTP que pueden serle útiles, como el proyecto RFxCopConsoleCS, que implementa una regla simple de estilo FxCop en Roslyn.

También debo mencionar que el analizador es completo para Roslyn, por lo que cuanto más pueda prescindir de la información semántica, mejor.

Cuestiones relacionadas