Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 246
Mitch Avatar asked Apr 24 '14 23:04

Mitch


2 Answers

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.

like image 93
Mitch Avatar answered Sep 17 '22 08:09

Mitch


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!

like image 20
Cameron Avatar answered Sep 19 '22 08:09

Cameron