This project is read-only.

Simplify document (qualified names)

Topics: APIs
Jan 9, 2015 at 2:05 PM
We're trying to achieve a simplification of our generated syntax tree, i.e., shorten qualified names and introduce using directives.

The tree is generated using the SyntaxFactory and is saved to a temporary document (single project in solution). Then we call Simplifier.ReduceAsync on the document, but without any result. We also added one using directive manually (using the SyntaxFactory), but even those types which belong to the namespace, are not shortened.

So what do we miss here? Thanks in advance.
Jan 10, 2015 at 1:38 AM
How are you generating the project that contains the document? Its hard to guess without seeing the code, but the simplifier needs to know about all the references in order to work. If, for some reason, your generated project has no references to things like mscorlib.dll then there is no way for the simplifier do much of anything.
Jan 10, 2015 at 2:11 AM
Just to make sure: you know that the simplified document is returned by the method, right?
Jan 10, 2015 at 9:25 PM
@jmarolf: There is an existing project that is being loaded. Then, I'm adding a new document to the project. Anything else that I should consider? How could I find out whether this is an issue?

@svick: I know the simplifier returns the document :/ So this shouldn't be the problem.
Jan 11, 2015 at 5:48 AM
The Simplifier only works on nodes with the Simplifier.Annotation attached, so if you haven't actually annotated the nodes the Simplifier won't do anything.
Jan 11, 2015 at 10:17 PM
Alright, I added the annotation to the root node with WithAdditionalAnnotations(Simplifier.Annotation) and created the document slighty different with Project.AddDocument("Temp", "").WithSyntaxRoot(dtoText). Now the document was formatted and System.String became string. However, I manually added the using directive for System. Could this happen automatically somehow?
Jan 12, 2015 at 6:28 AM
There is nothing currently available that adds using directives automatically. The Simplifier is able to simplify a name because the using directive is already there, making the explicit namespace redundant. You'll have to find your own solution.
Jan 13, 2015 at 12:01 PM
For completeness; without warranty :)
  public class TypeDescriptor
  {
    private readonly string _name;
    private readonly string _namespace;

    public TypeDescriptor (string name, string @namespace)
    {
      _name = name;
      _namespace = @namespace;
    }

    public string Name
    {
      get { return _name; }
    }

    public string Namespace
    {
      get { return _namespace; }
    }
  }

  public class TypeDescriptorFactory
  {
    private readonly SemanticModel _semanticModel;

    public TypeDescriptorFactory (SemanticModel semanticModel)
    {
      _semanticModel = semanticModel;
    }

    public TypeDescriptor Create (TypeDeclarationSyntax node)
    {
      return Create(_semanticModel.GetDeclaredSymbol(node));
    }

    public TypeDescriptor Create (FieldDeclarationSyntax node)
    {
      return Create(node.Declaration);
    }

    public TypeDescriptor Create (PropertyDeclarationSyntax node)
    {
      return Create(node.Type);
    }

    public TypeDescriptor Create (VariableDeclarationSyntax node)
    {
      return Create(node.Type);
    }

    public TypeDescriptor Create(TypeOfExpressionSyntax node)
    {
      return Create(node.Type);
    }

    public TypeDescriptor Create (AttributeSyntax typeInfo)
    {
      return Create(typeInfo.Name);
    }

    public TypeDescriptor Create (TypeSyntax node)
    {
      return Create(_semanticModel.GetTypeInfo(node));
    }

    public TypeDescriptor Create (TypeInfo typeInfo)
    {
      return Create(typeInfo.Type);
    }

    public static TypeDescriptor Create (ITypeSymbol typeSymbol)
    {
      var @namespace = typeSymbol.ContainingNamespace == null ? string.Empty : typeSymbol.ContainingNamespace.ToString();
      return new TypeDescriptor(typeSymbol.Name, @namespace);
    }
  }

  public class TypeDescriptorAggregator : CSharpSyntaxWalker
  {
    private readonly TypeDescriptorFactory _typeDescriptorFactory;
    private readonly HashSet<TypeDescriptor> _usedTypes;

    public TypeDescriptorAggregator (SemanticModel semanticModel)
    {
      _typeDescriptorFactory = new TypeDescriptorFactory(semanticModel);
      _usedTypes = new HashSet<TypeDescriptor>();
    }

    public HashSet<TypeDescriptor> UsedTypes
    {
      get { return _usedTypes; }
    }

    public override void VisitTypeOfExpression (TypeOfExpressionSyntax node)
    {
      var type = _typeDescriptorFactory.Create(node);
      _usedTypes.Add(type);
      base.VisitTypeOfExpression(node);
    }

    public override void VisitPropertyDeclaration (PropertyDeclarationSyntax node)
    {
      var type = _typeDescriptorFactory.Create(node);
      _usedTypes.Add(type);
      base.VisitPropertyDeclaration(node);
    }

    public override void VisitAttribute (AttributeSyntax node)
    {
      var type = _typeDescriptorFactory.Create(node);
      _usedTypes.Add(type);
      base.VisitAttribute(node);
    }
  }
The code can then be used to simplify the syntax tree like this:
    private Document Simplify (CompilationUnitSyntax generatedSyntax, Project project)
    {
      var tempDocument = project.AddDocument("Temp", "").WithSyntaxRoot(generatedSyntax);

      var walker = new TypeDescriptorAggregator(tempDocument.GetSemanticModelAsync().Result);
      walker.Visit(tempDocument.GetSyntaxRootAsync().Result);
      var namespaces = walker.UsedTypes.Select(x => x.Namespace).Distinct();
      var simplifiableSyntax = generatedSyntax
          .AddUsings(namespaces.Select(x => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(x))).ToArray())
          .WithAdditionalAnnotations(Simplifier.Annotation);

      return Simplifier.ReduceAsync(tempDocument.WithSyntaxRoot(simplifiableSyntax), _workspace.Options).Result;
    }