Attempting to run the T4 templates for Immutable Object Graph gives errors of
╔═══════╦═══╦══════════════════════════════════════════════════════════════════════════════════════════════════╦═════════════════════════════════════════════════════════╦═══╦════╦══════╗
║ Error ║ 5 ║ Compiling transformation: Invalid token 'this' in class, struct, or interface member declaration ║ c:\dev\ImmutableObjectGraph-master\2013\Demo\Message.tt ║ 1 ║ 1 ║ Demo ║
║ Error ║ 6 ║ Compiling transformation: Method must have a return type ║ c:\dev\ImmutableObjectGraph-master\2013\Demo\Message.tt ║ 1 ║ 6 ║ Demo ║
║ Error ║ 7 ║ Compiling transformation: Type expected ║ c:\dev\ImmutableObjectGraph-master\2013\Demo\Message.tt ║ 1 ║ 12 ║ Demo ║
╚═══════╩═══╩══════════════════════════════════════════════════════════════════════════════════════════════════╩═════════════════════════════════════════════════════════╩═══╩════╩══════╝
The line reported is always line 1, and the full set of t4 templates is many hundreds of lines. How do I troubleshoot and fix this issue?
You cannot have literals in a T4 template after a scriptlet.
Change
<#@ template debug="true" language="C#" #>
<#+
// scriptlet
#>
<-- empty line here
To
<#@ template debug="true" language="C#" #>
<#+
// scriptlet
#>
Debugging
You can see the C# being generated by the T4 engine by calling PreProcessTemplate
with a custom templating host.
I modified the Custom Template Host sample for this purpose:
using Microsoft.VisualStudio.TextTemplating;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CustomHost
{
class CustomCmdLineHost : ITextTemplatingEngineHost
{
public string TemplateFile { get; private set; }
public string FileExtension { get; private set; }
public Encoding FileEncoding { get; private set; }
public CompilerErrorCollection Errors { get; private set; }
public IList<string> StandardAssemblyReferences { get { return new[] { typeof(System.Uri).Assembly.Location }; } }
public IList<string> StandardImports { get { return new string[] { "System" }; } }
public CustomCmdLineHost(string file)
{
this.TemplateFile = file;
this.FileEncoding = Encoding.UTF8;
this.FileExtension = ".txt";
}
public bool LoadIncludeText(string requestFileName, out string content, out string location)
{
content = location = String.Empty;
if (File.Exists(requestFileName))
{
content = File.ReadAllText(requestFileName);
return true;
}
return false;
}
public object GetHostOption(string optionName)
{
object returnObject;
switch (optionName)
{
case "CacheAssemblies":
returnObject = true;
break;
default:
returnObject = null;
break;
}
return returnObject;
}
public string ResolveAssemblyReference(string assemblyReference)
{
return ResolvePath(assemblyReference);
}
public Type ResolveDirectiveProcessor(string processorName)
{
throw new Exception("Directive Processor not found");
}
public string ResolvePath(string fileName)
{
if (File.Exists(fileName))
{
return fileName;
}
string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
if (File.Exists(candidate))
{
return candidate;
}
return fileName;
}
public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
{
return String.Empty;
}
public void SetFileExtension(string extension)
{
FileExtension = extension;
}
public void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
{
FileEncoding = encoding;
}
public void LogErrors(CompilerErrorCollection errors)
{
Errors = errors;
}
public AppDomain ProvideTemplatingAppDomain(string content)
{
return AppDomain.CreateDomain("Generation App Domain");
}
}
class Program
{
static void Main(string[] args)
{
var templateFileName = args[0];
CustomCmdLineHost host = new CustomCmdLineHost(templateFileName);
Engine engine = new Engine();
string language;
string[] refs;
var output = engine.PreprocessTemplate(
// input file
File.ReadAllText(templateFileName), host,
"testClass", "testNamespace", out language, out refs);
string outputFileName = Path.Combine(
Path.GetDirectoryName(templateFileName),
templateFileName + ".generator.cs");
File.WriteAllText(outputFileName, output, host.FileEncoding);
foreach (CompilerError error in host.Errors)
Console.WriteLine(error.ToString());
Console.ReadLine();
}
}
}
Examining the transformer generated from the template showed lines like the following outside the TransformText()
method. Seemingly, any literals in the source templates which came after a scriptlet (<#+ #>
) would be placed in-fix into the generated generator class.
#line 1 "C:\dev\ImmutableObjectGraph-master\2013\Demo\Fruit.tt"
this.Write("\n");
Removing the newline characters at the end of each template file resolved the issue.
For me, the trailing newline at the end of the file was not a problem, but having Unix line endings (\n) instead of Windows line endings (\r\n) broke the T4 engine.
Very frustrating to debug when you have two files that look identical but one of them won't compile!
Yes, this was something to do with line ending for me too, possible due to source control.
To resolve, I copied and pasted the template code into Notepad++ and saved as a normal txt file then copy and pasted back.
(notepad didn't work as the line endings were wrong)