Skip to content
This repository was archived by the owner on Dec 12, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CodeGeneration.Roslyn/CodeGeneration.Roslyn.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<LangVersion>7.1</LangVersion>
Copy link
Owner

@AArnott AArnott Jul 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go ahead and shoot for 7.3. 😄 #Closed

<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\opensource.snk</AssemblyOriginatorKeyFile>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
Expand Down
35 changes: 22 additions & 13 deletions src/CodeGeneration.Roslyn/DocumentTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ public static async Task<SyntaxTree> TransformAsync(CSharpCompilation compilatio
var inputSyntaxTree = inputSemanticModel.SyntaxTree;
var inputCompilationUnit = inputSyntaxTree.GetCompilationUnitRoot();

var inputFileLevelExternAliases = inputCompilationUnit
var compilationUnitExterns = inputCompilationUnit
.Externs
.Select(x => x.WithoutTrivia())
.ToImmutableArray();
var inputFileLevelUsingDirectives = inputCompilationUnit
var compilationUnitUsings = inputCompilationUnit
.Usings
.Select(x => x.WithoutTrivia())
.ToImmutableArray();
var compilationUnitAttributeLists = ImmutableArray<AttributeListSyntax>.Empty;

var memberNodes = inputSyntaxTree.GetRoot().DescendantNodesAndSelf(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax || n is TypeDeclarationSyntax).OfType<CSharpSyntaxNode>();

Expand All @@ -67,26 +68,34 @@ public static async Task<SyntaxTree> TransformAsync(CSharpCompilation compilatio
var generators = FindCodeGenerators(attributeData, assemblyLoader);
foreach (var generator in generators)
{
var context = new TransformationContext(memberNode, inputSemanticModel, compilation, projectDirectory);
var generatedMembers = await generator.GenerateAsync(context, progress, CancellationToken.None);
var context = new TransformationContext(memberNode, inputSemanticModel, compilation, projectDirectory,
compilationUnitUsings, compilationUnitExterns);

// Figure out ancestry for the generated type, including nesting types and namespaces.
foreach (var ancestor in memberNode.Ancestors())
if (generator is IRichCodeGenerator richGenerator)
{
generatedMembers = WrapInAncestor(generatedMembers, ancestor);
var (members, usings, attributeLists, externs) = await richGenerator.GenerateRichAsync(context, progress, CancellationToken.None);
compilationUnitExterns = compilationUnitExterns.AddRange(externs);
compilationUnitUsings = compilationUnitUsings.AddRange(usings);
compilationUnitAttributeLists = compilationUnitAttributeLists.AddRange(attributeLists);
emittedMembers = emittedMembers.AddRange(members);
}
else
{
var generatedMembers = await generator.GenerateAsync(context, progress, CancellationToken.None);
// Figure out ancestry for the generated type, including nesting types and namespaces.
generatedMembers = memberNode.Ancestors().Aggregate(generatedMembers, WrapInAncestor);
emittedMembers = emittedMembers.AddRange(generatedMembers);
}

emittedMembers = emittedMembers.AddRange(generatedMembers);
}
}

// By default, retain all the using directives that came from the input file.
var resultFileLevelExternAliases = SyntaxFactory.List(inputFileLevelExternAliases);
var resultFileLevelUsingDirectives = SyntaxFactory.List(inputFileLevelUsingDirectives);
var resultFileLevelExterns = SyntaxFactory.List(compilationUnitExterns);
var resultFileLevelUsings = SyntaxFactory.List(compilationUnitUsings);

var compilationUnit = SyntaxFactory.CompilationUnit()
.WithExterns(resultFileLevelExternAliases)
.WithUsings(resultFileLevelUsingDirectives)
.WithExterns(resultFileLevelExterns)
.WithUsings(resultFileLevelUsings)
.WithMembers(emittedMembers)
.WithLeadingTrivia(SyntaxFactory.Comment(GeneratedByAToolPreamble))
.WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed)
Expand Down
19 changes: 19 additions & 0 deletions src/CodeGeneration.Roslyn/IRichCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.

namespace CodeGeneration.Roslyn
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;

/// <summary>
/// Describes a code generator that responds to attributes on members to generate code,
/// and returns compilation unit members
/// </summary>
public interface IRichCodeGenerator : ICodeGenerator
{
Task<RichGenerationResult> GenerateRichAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken);
}
}
67 changes: 67 additions & 0 deletions src/CodeGeneration.Roslyn/RichGenerationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.

namespace CodeGeneration.Roslyn
{
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

/// <summary>
/// Contains <see cref="CompilationUnitSyntax"/> additions generated by the <see cref="IRichCodeGenerator"/>.
/// </summary>
public struct RichGenerationResult
{
/// <summary>
/// Creates <see cref="RichGenerationResult"/> with provided arguments as property values.
/// </summary>
/// <param name="members">Assigned to <see cref="Members"/>.</param>
/// <param name="usings">Assigned to <see cref="Usings"/>.</param>
/// <param name="attributeLists">Assigned to <see cref="AttributeLists"/>.</param>
/// <param name="externs">Assigned to <see cref="Externs"/>.</param>
[DebuggerStepThrough]
public RichGenerationResult(
SyntaxList<MemberDeclarationSyntax> members,
SyntaxList<UsingDirectiveSyntax> usings = default,
SyntaxList<AttributeListSyntax> attributeLists = default,
SyntaxList<ExternAliasDirectiveSyntax> externs = default)
{
Copy link
Owner

@AArnott AArnott Aug 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of these ctors, which will be troublesome to version as we add more members, why not give the properties public setters and let them set whatever ones they want? #Closed

Members = members;
Usings = usings;
AttributeLists = attributeLists;
Externs = externs;
}

/// <summary>
/// Gets <see cref="MemberDeclarationSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<MemberDeclarationSyntax> Members { get; }

/// <summary>
/// Gets <see cref="UsingDirectiveSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<UsingDirectiveSyntax> Usings { get; }

/// <summary>
/// Gets <see cref="ExternAliasDirectiveSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<ExternAliasDirectiveSyntax> Externs { get; }

/// <summary>
/// Gets <see cref="AttributeListSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<AttributeListSyntax> AttributeLists { get; }

[DebuggerHidden, DebuggerStepThrough]
public void Deconstruct(out SyntaxList<MemberDeclarationSyntax> members,
out SyntaxList<UsingDirectiveSyntax> usings,
out SyntaxList<AttributeListSyntax> attributeLists,
out SyntaxList<ExternAliasDirectiveSyntax> externs)
{
members = Members;
usings = Usings;
attributeLists = AttributeLists;
externs = Externs;
}
}
Copy link
Owner

@AArnott AArnott Aug 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of deconstruction unless it's very obvious what the members will be to the caller and what order they should come in (i.e. first and last name). As the members grow, their order in this list are basically arbitrary -- they look arbitrary right now. That leads to bugs in code when you add/refactor in this class but fail to update every caller (which is hard, since they don't actually call Deconstruct explicitly) and it happens to compile because the types between two reordered members remains the same.
It also requires that the caller explicitly name the variables when it would produce smaller, more readable code if they just dereferenced the property as it is defined in this struct each time. #Closed

}
25 changes: 20 additions & 5 deletions src/CodeGeneration.Roslyn/TransformationContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
using Microsoft.CodeAnalysis;
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.

using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace CodeGeneration.Roslyn
{
Expand All @@ -12,13 +17,19 @@ public class TransformationContext
/// <param name="semanticModel">The semantic model.</param>
/// <param name="compilation">The overall compilation being generated for.</param>
/// <param name="projectDirectory">The absolute path of the directory where the project file is located</param>
/// <param name="compilationUnitUsings">The using directives already queued to be generated.</param>
/// <param name="compilationUnitExterns">The extern aliases already queued to be generated.</param>
public TransformationContext(CSharpSyntaxNode processingNode, SemanticModel semanticModel, CSharpCompilation compilation,
string projectDirectory)
string projectDirectory,
IEnumerable<UsingDirectiveSyntax> compilationUnitUsings,
IEnumerable<ExternAliasDirectiveSyntax> compilationUnitExterns)
{
Copy link
Owner

@AArnott AArnott Aug 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is a technically breaking change, it should only break unit tests that may have been written for generators, which isn't as severe.
We could make this internal, but that would make mocking up those unit tests even harder. So I'm good with this change. #ByDesign

ProcessingNode = processingNode;
SemanticModel = semanticModel;
Compilation = compilation;
ProjectDirectory = projectDirectory;
CompilationUnitUsings = compilationUnitUsings;
CompilationUnitExterns = compilationUnitExterns;
}

/// <summary>Gets the syntax node the generator attribute is found on.</summary>
Expand All @@ -30,9 +41,13 @@ public TransformationContext(CSharpSyntaxNode processingNode, SemanticModel sema
/// <summary>Gets the overall compilation being generated for.</summary>
public CSharpCompilation Compilation { get; }

/// <summary>
/// Gets the absolute path of the directory where the project file is located
/// </summary>
/// <summary>Gets the absolute path of the directory where the project file is located.</summary>
public string ProjectDirectory { get; }

/// <summary>Gets a collection of using directives already queued to be generated.</summary>
public IEnumerable<UsingDirectiveSyntax> CompilationUnitUsings { get; }

/// <summary>Gets a collection of extern aliases already queued to be generated.</summary>
public IEnumerable<ExternAliasDirectiveSyntax> CompilationUnitExterns { get; }
}
}