visual-studio-2013t4

T4 reports Compiling transformation: Invalid token 'this' in class, struct


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?


Solution

  • 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.