Skip to content
This repository was archived by the owner on Dec 12, 2020. It is now read-only.

Commit 078476a

Browse files
authoredMay 2, 2018
Merge pull request #67 from Pzixel/feature/global_attributes_generation
Assembly attributes support
2 parents b889381 + 7383a10 commit 078476a

File tree

12 files changed

+117
-35
lines changed

12 files changed

+117
-35
lines changed
 

‎README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ If you build with `dotnet build`, you need to take a couple extra steps. First,
163163
Second, add this item to an `<ItemGroup>` in the project that will execute the code generator as part of your build:
164164

165165
```xml
166-
<DotNetCliToolReference Include="dotnet-codegen" Version="0.4.11" />
166+
<DotNetCliToolReference Include="dotnet-codegen" Version="0.4.12" />
167167
```
168168

169169
You should adjust the version in the above xml to match the version of this tool you are using.
@@ -187,10 +187,10 @@ to immediately see the effects of your changes on the generated code.
187187
You can also package up your code generator as a NuGet package for others to install
188188
and use. Your NuGet package should include a dependency on the `CodeGeneration.Roslyn.BuildTime`
189189
that matches the version of `CodeGeneration.Roslyn` that you used to produce your generator.
190-
For example, if you used version 0.4.11 of this project, your .nuspec file would include this tag:
190+
For example, if you used version 0.4.12 of this project, your .nuspec file would include this tag:
191191

192192
```xml
193-
<dependency id="CodeGeneration.Roslyn.BuildTime" version="0.4.11" />
193+
<dependency id="CodeGeneration.Roslyn.BuildTime" version="0.4.12" />
194194
```
195195

196196
In addition to this dependency, your NuGet package should include a `build` folder with an
@@ -219,7 +219,7 @@ so that the MSBuild Task can invoke the `dotnet codegen` command line tool:
219219
```xml
220220
<ItemGroup>
221221
<PackageReference Include="YourCodeGenPackage" Version="1.2.3" PrivateAssets="all" />
222-
<DotNetCliToolReference Include="dotnet-codegen" Version="0.4.11" />
222+
<DotNetCliToolReference Include="dotnet-codegen" Version="0.4.12" />
223223
</ItemGroup>
224224
```
225225

‎src/CodeGeneration.Roslyn.Tasks/GenerateCodeFromAttributes.cs

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class GenerateCodeFromAttributes : ToolTask
2222
[Required]
2323
public ITaskItem[] GeneratorAssemblySearchPaths { get; set; }
2424

25+
[Required]
26+
public string ProjectDirectory { get; set; }
27+
2528
[Required]
2629
public string IntermediateOutputDirectory { get; set; }
2730

@@ -80,6 +83,9 @@ protected override string GenerateResponseFileCommands()
8083
argBuilder.AppendLine("--out");
8184
argBuilder.AppendLine(this.IntermediateOutputDirectory);
8285

86+
argBuilder.AppendLine("--projectDir");
87+
argBuilder.AppendLine(this.ProjectDirectory);
88+
8389
this.generatedCompileItemsFilePath = Path.Combine(this.IntermediateOutputDirectory, Path.GetRandomFileName());
8490
argBuilder.AppendLine("--generatedFilesList");
8591
argBuilder.AppendLine(this.generatedCompileItemsFilePath);

‎src/CodeGeneration.Roslyn.Tasks/build/CodeGeneration.Roslyn.BuildTime.targets

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<Target Name="GenerateCodeFromAttributes" DependsOnTargets="ResolveReferences" BeforeTargets="CoreCompile">
2020
<GenerateCodeFromAttributes
2121
ToolLocationOverride="$(GenerateCodeFromAttributesToolPathOverride)"
22+
ProjectDirectory="$(MSBuildProjectDirectory)"
2223
Compile="@(Compile)"
2324
ReferencePath="@(ReferencePath)"
2425
GeneratorAssemblySearchPaths="@(GeneratorAssemblySearchPaths)"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Andrew Arnott. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
3+
4+
namespace CodeGeneration.Roslyn.Tests.Generators
5+
{
6+
using System;
7+
using System.Diagnostics;
8+
using Validation;
9+
10+
[AttributeUsage(AttributeTargets.Assembly)]
11+
[CodeGenerationAttribute(typeof(DirectoryPathGenerator))]
12+
[Conditional("CodeGeneration")]
13+
public class DirectoryPathAttribute : Attribute
14+
{
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Andrew Arnott. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
3+
4+
namespace CodeGeneration.Roslyn.Tests.Generators
5+
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Validation;
13+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
14+
15+
public class DirectoryPathGenerator : ICodeGenerator
16+
{
17+
public DirectoryPathGenerator(AttributeData attributeData)
18+
{
19+
Requires.NotNull(attributeData, nameof(attributeData));
20+
}
21+
22+
public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken)
23+
{
24+
var member = ClassDeclaration("DirectoryPathTest")
25+
.AddMembers(
26+
FieldDeclaration(
27+
VariableDeclaration(
28+
PredefinedType(Token(SyntaxKind.StringKeyword)))
29+
.AddVariables(
30+
VariableDeclarator(Identifier("Path"))
31+
.WithInitializer(
32+
EqualsValueClause(
33+
LiteralExpression(
34+
SyntaxKind.StringLiteralExpression,
35+
Literal(context.ProjectDirectory))))))
36+
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword))));
37+
38+
return Task.FromResult(List<MemberDeclarationSyntax>(new []{member}));
39+
}
40+
}
41+
}

‎src/CodeGeneration.Roslyn.Tests.Generators/DuplicateWithSuffixGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationCon
3535
var results = SyntaxFactory.List<MemberDeclarationSyntax>();
3636

3737
MemberDeclarationSyntax copy = null;
38-
var applyToClass = context.ProcessingMember as ClassDeclarationSyntax;
38+
var applyToClass = context.ProcessingNode as ClassDeclarationSyntax;
3939
if (applyToClass != null)
4040
{
4141
copy = applyToClass

‎src/CodeGeneration.Roslyn.Tests.Generators/MultiplySuffixGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationCon
2929
var results = SyntaxFactory.List<MemberDeclarationSyntax>();
3030

3131
MemberDeclarationSyntax copy = null;
32-
var applyToClass = context.ProcessingMember as ClassDeclarationSyntax;
32+
var applyToClass = context.ProcessingNode as ClassDeclarationSyntax;
3333
if (applyToClass != null)
3434
{
3535
var properties = applyToClass.Members.OfType<PropertyDeclarationSyntax>()

‎src/CodeGeneration.Roslyn.Tests/CodeGenerationTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
33

44
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
85
using CodeGeneration.Roslyn.Tests.Generators;
96
using Xunit;
107

8+
[assembly: DirectoryPath]
9+
1110
public partial class CodeGenerationTests
1211
{
1312
/// <summary>
@@ -21,6 +20,7 @@ public void SimpleGenerationWorks()
2120
var fooB = new CodeGenerationTests.FooB();
2221
var multiplied = new MultipliedBar();
2322
multiplied.ValueSuff1020();
23+
Assert.EndsWith(@"src\CodeGeneration.Roslyn.Tests", DirectoryPathTest.Path, StringComparison.OrdinalIgnoreCase);
2424
}
2525

2626
[DuplicateWithSuffixByName("A")]

‎src/CodeGeneration.Roslyn.Tool/Program.cs

+3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ static int Main(string[] args)
1818
IReadOnlyList<string> generatorSearchPaths = Array.Empty<string>();
1919
string generatedCompileItemFile = null;
2020
string outputDirectory = null;
21+
string projectDir = null;
2122
ArgumentSyntax.Parse(args, syntax =>
2223
{
2324
syntax.DefineOptionList("r|reference", ref refs, "Paths to assemblies being referenced");
2425
syntax.DefineOptionList("generatorSearchPath", ref generatorSearchPaths, "Paths to folders that may contain generator assemblies");
2526
syntax.DefineOption("out", ref outputDirectory, true, "The directory to write generated source files to");
27+
syntax.DefineOption("projectDir", ref projectDir, true, "The absolute path of the directory where the project file is located");
2628
syntax.DefineOption("generatedFilesList", ref generatedCompileItemFile, "The path to the file to create with a list of generated source files");
2729
syntax.DefineParameterList("compile", ref compile, "Source files included in compilation");
2830
});
@@ -41,6 +43,7 @@ static int Main(string[] args)
4143

4244
var generator = new CompilationGenerator
4345
{
46+
ProjectDirectory = projectDir,
4447
Compile = compile,
4548
ReferencePath = refs,
4649
GeneratorAssemblySearchPaths = generatorSearchPaths,

‎src/CodeGeneration.Roslyn/CompilationGenerator.cs

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public class CompilationGenerator
6565
/// </summary>
6666
public IEnumerable<string> EmptyGeneratedFiles => this.emptyGeneratedFiles;
6767

68+
public string ProjectDirectory { get; set; }
69+
6870
public void Generate(IProgress<Diagnostic> progress = null, CancellationToken cancellationToken = default(CancellationToken))
6971
{
7072
Verify.Operation(this.Compile != null, $"{nameof(Compile)} must be set first.");
@@ -104,6 +106,7 @@ public class CompilationGenerator
104106
var generatedSyntaxTree = DocumentTransform.TransformAsync(
105107
compilation,
106108
inputSyntaxTree,
109+
this.ProjectDirectory,
107110
this.LoadAssembly,
108111
progress).GetAwaiter().GetResult();
109112

‎src/CodeGeneration.Roslyn/DocumentTransform.cs

+25-19
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ namespace CodeGeneration.Roslyn
55
{
66
using System;
77
using System.Collections.Generic;
8-
using System.IO;
8+
using System.Collections.Immutable;
99
using System.Linq;
1010
using System.Reflection;
1111
using System.Text;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14-
using Microsoft;
1514
using Microsoft.CodeAnalysis;
1615
using Microsoft.CodeAnalysis.CSharp;
1716
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -40,7 +39,7 @@ public static class DocumentTransform
4039
/// <param name="assemblyLoader">A function that can load an assembly with the given name.</param>
4140
/// <param name="progress">Reports warnings and errors in code generation.</param>
4241
/// <returns>A task whose result is the generated document.</returns>
43-
public static async Task<SyntaxTree> TransformAsync(CSharpCompilation compilation, SyntaxTree inputDocument, Func<AssemblyName, Assembly> assemblyLoader, IProgress<Diagnostic> progress)
42+
public static async Task<SyntaxTree> TransformAsync(CSharpCompilation compilation, SyntaxTree inputDocument, string projectDirectory, Func<AssemblyName, Assembly> assemblyLoader, IProgress<Diagnostic> progress)
4443
{
4544
Requires.NotNull(compilation, nameof(compilation));
4645
Requires.NotNull(inputDocument, nameof(inputDocument));
@@ -51,16 +50,16 @@ public static async Task<SyntaxTree> TransformAsync(CSharpCompilation compilatio
5150

5251
var inputFileLevelUsingDirectives = inputSyntaxTree.GetRoot().ChildNodes().OfType<UsingDirectiveSyntax>();
5352

54-
var memberNodes = from syntax in inputSyntaxTree.GetRoot().DescendantNodes(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax || n is TypeDeclarationSyntax).OfType<MemberDeclarationSyntax>()
55-
select syntax;
53+
var memberNodes = inputSyntaxTree.GetRoot().DescendantNodesAndSelf(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax || n is TypeDeclarationSyntax).OfType<CSharpSyntaxNode>();
5654

5755
var emittedMembers = SyntaxFactory.List<MemberDeclarationSyntax>();
5856
foreach (var memberNode in memberNodes)
5957
{
60-
var generators = FindCodeGenerators(inputSemanticModel, memberNode, assemblyLoader);
58+
var attributeData = GetAttributeData(compilation, inputSemanticModel, memberNode);
59+
var generators = FindCodeGenerators(attributeData, assemblyLoader);
6160
foreach (var generator in generators)
6261
{
63-
var context = new TransformationContext(memberNode, inputSemanticModel, compilation);
62+
var context = new TransformationContext(memberNode, inputSemanticModel, compilation, projectDirectory);
6463
var generatedTypes = await generator.GenerateAsync(context, progress, CancellationToken.None);
6564

6665
// Figure out ancestry for the generated type, including nesting types and namespaces.
@@ -112,22 +111,29 @@ public static async Task<SyntaxTree> TransformAsync(CSharpCompilation compilatio
112111
return compilationUnit.SyntaxTree;
113112
}
114113

115-
private static IEnumerable<ICodeGenerator> FindCodeGenerators(SemanticModel document, SyntaxNode nodeWithAttributesApplied, Func<AssemblyName, Assembly> assemblyLoader)
114+
private static ImmutableArray<AttributeData> GetAttributeData(Compilation compilation, SemanticModel document, SyntaxNode syntaxNode)
116115
{
117-
Requires.NotNull(document, "document");
118-
Requires.NotNull(nodeWithAttributesApplied, "nodeWithAttributesApplied");
116+
Requires.NotNull(document, nameof(document));
117+
Requires.NotNull(syntaxNode, nameof(syntaxNode));
119118

120-
var symbol = document.GetDeclaredSymbol(nodeWithAttributesApplied);
121-
if (symbol != null)
119+
switch (syntaxNode)
122120
{
123-
foreach (var attributeData in symbol.GetAttributes())
121+
case CompilationUnitSyntax syntax:
122+
return compilation.Assembly.GetAttributes().Where(x => x.ApplicationSyntaxReference.SyntaxTree == syntax.SyntaxTree).ToImmutableArray();
123+
default:
124+
return document.GetDeclaredSymbol(syntaxNode)?.GetAttributes() ?? ImmutableArray<AttributeData>.Empty;
125+
}
126+
}
127+
128+
private static IEnumerable<ICodeGenerator> FindCodeGenerators(ImmutableArray<AttributeData> nodeAttributes, Func<AssemblyName, Assembly> assemblyLoader)
129+
{
130+
foreach (var attributeData in nodeAttributes)
131+
{
132+
Type generatorType = GetCodeGeneratorTypeForAttribute(attributeData.AttributeClass, assemblyLoader);
133+
if (generatorType != null)
124134
{
125-
Type generatorType = GetCodeGeneratorTypeForAttribute(attributeData.AttributeClass, assemblyLoader);
126-
if (generatorType != null)
127-
{
128-
ICodeGenerator generator = (ICodeGenerator)Activator.CreateInstance(generatorType, attributeData);
129-
yield return generator;
130-
}
135+
ICodeGenerator generator = (ICodeGenerator)Activator.CreateInstance(generatorType, attributeData);
136+
yield return generator;
131137
}
132138
}
133139
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
using System;
2-
using Microsoft.CodeAnalysis;
1+
using Microsoft.CodeAnalysis;
32
using Microsoft.CodeAnalysis.CSharp;
4-
using Microsoft.CodeAnalysis.CSharp.Syntax;
53

64
namespace CodeGeneration.Roslyn
75
{
@@ -10,23 +8,31 @@ public class TransformationContext
108
/// <summary>
119
/// Initializes a new instance of the <see cref="TransformationContext" /> class.
1210
/// </summary>
13-
/// <param name="processingMember">The syntax node the generator attribute is found on.</param>
11+
/// <param name="processingNode">The syntax node the generator attribute is found on.</param>
1412
/// <param name="semanticModel">The semantic model.</param>
1513
/// <param name="compilation">The overall compilation being generated for.</param>
16-
public TransformationContext(MemberDeclarationSyntax processingMember, SemanticModel semanticModel, CSharpCompilation compilation)
14+
/// <param name="projectDirectory">The absolute path of the directory where the project file is located</param>
15+
public TransformationContext(CSharpSyntaxNode processingNode, SemanticModel semanticModel, CSharpCompilation compilation,
16+
string projectDirectory)
1717
{
18-
ProcessingMember = processingMember;
18+
ProcessingNode = processingNode;
1919
SemanticModel = semanticModel;
2020
Compilation = compilation;
21+
ProjectDirectory = projectDirectory;
2122
}
2223

2324
/// <summary>Gets the syntax node the generator attribute is found on.</summary>
24-
public MemberDeclarationSyntax ProcessingMember { get; }
25+
public CSharpSyntaxNode ProcessingNode { get; }
2526

2627
/// <summary>Gets the semantic model for the <see cref="Compilation" />.</summary>
2728
public SemanticModel SemanticModel { get; }
2829

2930
/// <summary>Gets the overall compilation being generated for.</summary>
3031
public CSharpCompilation Compilation { get; }
32+
33+
/// <summary>
34+
/// Gets the absolute path of the directory where the project file is located
35+
/// </summary>
36+
public string ProjectDirectory { get; }
3137
}
3238
}

0 commit comments

Comments
 (0)
Failed to load comments.