How to call the compiler from .NET?

Topics: General
Oct 3, 2012 at 3:19 AM

Is there any way to call the compiler from .NET other than running as an external process?

Oct 3, 2012 at 3:33 AM

BTW, I have tried referencing the DLL and EXE files that come with the VS package, the reference dialog complained none of them was .NET or COM.

Also, tried opening the visual studio extension as a zip file and checking the language service DLL, but didn't know what to call in that DLL.

Any other suggestions?

Oct 3, 2012 at 7:37 AM

I was wondering about this too.. My rather hacky method is to copy the tsc.exe, tsc.js and tschost.dll to a directory and call them. But of course I'd prefer a clean way too.

Oct 3, 2012 at 10:06 AM

typescript is written in typescript (javascript). Why not use Jurassic ....

Oct 3, 2012 at 7:59 PM

If you want an fully integrated execution in your .net app you will have to use Jurassic or JavaScript.net from codeplex. JavaScript.net is faster.
I planning to use cscript.exe and start a new process

Oct 3, 2012 at 8:32 PM

I tried using JavaScript.NET,

Got the following error when running the compiler with no files:

TypeError: Cannot read property 'watchFiles' of null

The code was quite simple, just a Console app:

namespace SampleTs
{
   class Program
    {
        static void Main(string[] args)
        {
            var compiler = File.ReadAllText("tsc.js");
            using(var context = new JavascriptContext())
            {
                context.Run(compiler);
            }
        }
    }
}

The property mentioned was already in some IO related thing.

Any ideas what I may be missing?  I'll check Jurassic  anyway.

Oct 3, 2012 at 8:48 PM
Edited Oct 3, 2012 at 8:48 PM

I tried with no luck as well.

I think I'm getting the same error with different message:

TypeError: null cannot be converted to an object

The code is still very simple, the same Console app (which has tsc.js and typescript.js included as content files that are copied to output dir if newer, and I conirmed by debugging the tsc.js is written correctly):

namespace SampleTs
{
   class Program
    {
        static void Main(string[] args)
        {
            var javaScript = new Jurassic.ScriptEngine();
            javaScript.ExecuteFile("tsc.js");
        }
    }
}

Sounds like I need to set some environment thing, but I'm not sure what it may be.

Just installing via node, running Visual Studio plugin, or cscript, are all working. I'm just hoping to get something a bit quicker that's all .NET.

Oct 3, 2012 at 11:36 PM

So, the JavaScript.NET thing is more promising, although it's obvious some property "ioHost" is always null and that's why the first use of one of its properties fail.

Is there anything I can do using context.SetParameter() to give that a proper value? Am I on the right path to get it running at all?

I also tried processing both the "typescript.js" and "tsc.js" in one go (having text from "tsc.js" last), and got the same error. Running just "typescript.js" doesn't give errors, but doesn't print any compiler messages either.

Oct 4, 2012 at 6:08 AM

I managed to get it working with JavaScript.NET.

I needed the typescript.js file, not tsc.js, looking at the play page source code, I could put the following together and get it working:

static void Main(string[] args)
{
    var tscFile = File.ReadAllText("tsc.js");
    var typeScriptFile = File.ReadAllText("typescript.js");

    var source = new StringBuilder();

    const string createCompiler = @"(function(typeScriptSource) {
    var outfile = { 
            source: '', Write: function (s) { this.source += s; }, 
            WriteLine: function (s) { this.source += s + '\n'; }, 
            Close: function () { } };
    var outerr = { Write: function (s) { }, WriteLine: function (s) { }, Close: function () { } };
    var parseErrors = [];
    // var Tools = require('typescript');
    compiler = new TypeScript.TypeScriptCompiler(outfile, outerr);
    compiler.setErrorCallback(function (start, len, message) 
        { parseErrors.push({ start: start, len: len, message: message }); }
    );
    compiler.parser.errorRecovery = true;
    // compiler.addUnit(libStr, 'lib.ts', true);
    compiler.addUnit(typeScriptSource, '');
    compiler.typeCheck();
    compiler.emit(false, function createFile(fileName) { return outfile; });
    return outfile.source;
    })(typeScriptSource);";

    source.AppendLine(typeScriptFile);
    source.Append(createCompiler);

    using (var context = new Noesis.Javascript.JavascriptContext())
    {
        context.SetParameter("typeScriptSource", "function(x:string){return x;}");  
        var result = context.Run(source.ToString());
        Console.WriteLine(result);
    }
}

This is very dirty still, and very simple, requireJS stuff doesn't work, there is a whole lot of stuff about path resolution and referencing multiple files with base folder path and more that needs to be done. I'll make it a bit cleaner and blog it,but posting the quick and dirty version now for anyone else interested in the same.

 

Oct 4, 2012 at 3:47 PM

Another way would be to use the MS Script Control to host the J(ava)Script engine in C#, throw TypeScript at it and then use some dynamic magic to get back the output and errors. So just for kicks, here's my crack at Mohamad's version using JavaScript.NET doing exactly that (also posted as a gist):

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

static class Program
{
    static int Run(IEnumerable<string> args)
    {
        var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        var npmModulesPath = Path.Combine(appDataPath, "npm", "node_modules");
        var tsBinPath = Path.Combine(npmModulesPath, "typescript", "bin");

        var sourceFilePath = args.FirstOrDefault(a => !string.IsNullOrWhiteSpace(a));
        var ts = sourceFilePath == null || sourceFilePath == "-" 
               ? Console.In.ReadToEnd() 
               : File.ReadAllText(sourceFilePath);
        
        var scType = Type.GetTypeFromProgID("MSScriptControl.ScriptControl.1");
        dynamic sc = Activator.CreateInstance(scType);
        sc.Language = "JavaScript";

        var tsPath = Path.Combine(tsBinPath, "typescript.js");
        if (!File.Exists(tsPath))
            throw new FileNotFoundException(@"TypeScript does not appear to be installed as an NPM global package. See http://www.typescriptlang.org/ for more information.", tsBinPath);

        var compiler = File.ReadAllText(tsPath);
        sc.AddCode(compiler);
        
        var resultObject = sc.Eval(@"
            (function(source) {
                var nop = function() {};
                var outfile = { 
                    source: [],
                    Write: function (s) { this.source.push(s); }, 
                    WriteLine: function (s) { this.Write(s + '\n'); }, 
                    Close: nop
                };
                var outerr = { Write: nop, WriteLine: nop, Close: nop };
                var errors = [];
                var compiler = new TypeScript.TypeScriptCompiler(outfile, outerr);
                compiler.setErrorCallback(function (start, len, msg) { 
                    errors.push({ start: start, len: len, message: msg }); });
                compiler.parser.errorRecovery = true;
                compiler.addUnit(source, '');
                compiler.typeCheck();
                compiler.emit(false, function() { return outfile; });
                return {
                    output: outfile.source.join(''), 
                    errors: errors
                };
            })(" + ts.ToJson() + @");");
        
        var errors = 
            from dynamic error in (IEnumerable) resultObject.errors
            select new
            {
                Start = (int) error.start, 
                Length = (int) error.len, 
                Message = (string) error.message,
            } 
            into error
            where error.Start >= 0
            select string.Format(@"({0},{1}): {2}", 
                                 error.Start, error.Length, error.Message);

        errors = errors.ToArray();
        Array.ForEach((string[]) errors, Console.Error.WriteLine);

        Console.WriteLine((string) resultObject.output);

        return !errors.Any() ? 0 : 1;
    }

    [STAThread]
    static int Main(string[] args)
    {
        try
        {
            return Run(args);
        }
        catch (Exception e)
        {
            Console.Error.WriteLine(e.ToString());
            return 0xbad;
        }
    }
    
    static string ToJson(this string s)
    {
        var length = (s = s ?? string.Empty).Length;
        var sb = new StringBuilder().Append('"');
        for (var index = 0; index < length; index++)
        {
            var ch = s[index];
            switch (ch)
            {
                case '\\':
                case '"':
                {
                    sb.Append('\\');
                    sb.Append(ch);
                    break;
                }
                case '\b': sb.Append("\\b"); break;
                case '\t': sb.Append("\\t"); break;
                case '\n': sb.Append("\\n"); break;
                case '\f': sb.Append("\\f"); break;
                case '\r': sb.Append("\\r"); break;
                default:
                {
                    if (ch < ' ')
                    {
                        sb.Append("\\u");
                        sb.Append(((int)ch).ToString("x4", NumberFormatInfo.InvariantInfo));
                    }
                    else
                    {
                        sb.Append(ch);
                    }
                    break;
                }
            }
        }
        return sb.Append('"').ToString();
    }
}

Oct 4, 2012 at 8:41 PM

tsc.exe is creating a COM instance of the IE Chakra engine and the ProcessDebugger7 COM object, wiring it together and executing the tsc.js script. Thats' all, nothing fancy there.

The proper construction and execution of the Chakra engine can be found here in Sass and Coffee's code:

https://github.com/xpaulbettsx/SassAndCoffee/tree/master/SassAndCoffee.JavaScript

Hope this helps you to write a proper solution here.

 

Oct 5, 2012 at 1:13 AM

One side effect of using the current tsc is that it requires outputing to a file. If you are interested in running it in process, you are probably interested in getting the output back in-memory (or else, you wouldn't be bothered by running it as external process).