Reputation:
I have a custom attribute I'm using to test a Roslyn code analyzer I'm writing:
[AttributeUsage( validOn: AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true )]
public class DummyAttribute : Attribute
{
public DummyAttribute( string arg1, Type arg2 )
{
}
public int TestField;
}
which decorates another test class:
[Dummy( "", typeof( string ) )]
[Dummy( "test", typeof( int ) )]
[Dummy( "test", typeof( int ) )]
public class J4JLogger<TCalling> : IJ4JLogger<TCalling>
{
But when I call GetSymbolInfo() on it with the semantic model it's defined in:
model.GetSymbolInfo( attrNode ).Symbol
the value of Symbol is null.
What's odd is that the GetSymbolInfo() call works perfectly well for attribute classes defined in the Net Core library (e.g., AttributeUsage
).
Both Dummy
and J4JLogger
are defined in the same project. I create my compilation unit by parsing the files individually (e.g., J4JLogger
is parsed and analyzed separately from Dummy
) so when I'm parsing J4JLogger
there is no reference to the assembly containing both J4JLogger
and Dummy
.
Could the problem be that model
doesn't actually contain the Dummy
class I think it does? Is there a way to check what's in the semantic model? Do I have to include a reference to the assembly whose source file I'm analyzing in the semantic model?
Corrected Parsing Logic
My original parsing logic parsed each file into a syntax tree independent of all its sister source files. The correct way to parse source files -- at least when they depend on each other -- is something like this:
protected virtual (CompilationUnitSyntax root, SemanticModel model) ParseMultiple( string primaryPath, params string[] auxPaths )
{
if( !IsValid )
return (null, null);
CSharpCompilation compilation;
SyntaxTree primaryTree;
var auxFiles = auxPaths == null || auxPaths.Length == 0
? new List<string>()
: auxPaths.Distinct().Where( p => !p.Equals( primaryPath, StringComparison.OrdinalIgnoreCase ) );
try
{
var auxTrees = new List<SyntaxTree>();
primaryTree = CSharpSyntaxTree.ParseText( File.ReadAllText( primaryPath ) );
auxTrees.Add( primaryTree );
foreach( var auxFile in auxFiles )
{
var auxTree = CSharpSyntaxTree.ParseText( File.ReadAllText( auxFile ) );
auxTrees.Add( auxTree );
}
compilation = CSharpCompilation.Create( ProjectDocument.AssemblyName )
.AddReferences( GetReferences().ToArray() )
.AddSyntaxTrees( auxTrees );
}
catch( Exception e )
{
Logger.Error<string>( "Configuration failed, exception message was {0}", e.Message );
return (null, null);
}
return (primaryTree.GetCompilationUnitRoot(), compilation.GetSemanticModel( primaryTree ));
}
A minor gotcha is that AddSyntaxTrees() does not appear to be incremental; you need to add all the relevant syntax trees in one call to AddSyntaxTrees().
Upvotes: 1
Views: 706
Reputation:
Turns out the problem was that you have to include all the source files that reference each other (e.g., Dummy
and J4JLogger
in my case) in the compilation unit because otherwise the "internal" references (e.g., decorating J4JLogger
with Dummy
) won't resolve. I've annotated the question with how I rewrote my parsing logic.
Upvotes: 1