Skip to content

Commit b27267e

Browse files
committed
Fixing TypeScript compilation output path issue (#1520)
1 parent ad1e4e7 commit b27267e

14 files changed

+328
-145
lines changed

src/WebJobs.Script/Description/Node/Compilation/JavaScriptCompilationServiceFactory.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,25 @@
44
using System;
55
using System.Collections.Immutable;
66
using System.Globalization;
7+
using System.IO;
8+
using System.Linq;
9+
using Microsoft.Azure.WebJobs.Script.Config;
710
using Microsoft.Azure.WebJobs.Script.Description.Node.TypeScript;
811

912
namespace Microsoft.Azure.WebJobs.Script.Description
1013
{
1114
public class JavaScriptCompilationServiceFactory : ICompilationServiceFactory<ICompilationService<IJavaScriptCompilation>, FunctionMetadata>
1215
{
16+
private const string TypeScriptToolName = "tsc.exe";
1317
private static readonly ImmutableArray<ScriptType> SupportedScriptTypes = new[] { ScriptType.Javascript, ScriptType.TypeScript }.ToImmutableArray();
18+
private readonly ScriptHost _host;
19+
private readonly Lazy<TypeScriptCompilationOptions> _typeScriptOptions;
20+
21+
public JavaScriptCompilationServiceFactory(ScriptHost host)
22+
{
23+
_host = host;
24+
_typeScriptOptions = new Lazy<TypeScriptCompilationOptions>(CreateTypeScriptCompilationOptions);
25+
}
1426

1527
ImmutableArray<ScriptType> ICompilationServiceFactory<ICompilationService<IJavaScriptCompilation>, FunctionMetadata>.SupportedScriptTypes => SupportedScriptTypes;
1628

@@ -21,11 +33,40 @@ public ICompilationService<IJavaScriptCompilation> CreateService(ScriptType scri
2133
case ScriptType.Javascript:
2234
return new RawJavaScriptCompilationService(metadata);
2335
case ScriptType.TypeScript:
24-
return new TypeScriptCompilationService();
36+
return new TypeScriptCompilationService(_typeScriptOptions.Value);
2537
default:
2638
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture,
2739
"The script type {0} is not supported by the {1}", scriptType, typeof(JavaScriptCompilationServiceFactory).Name));
2840
}
2941
}
42+
43+
private TypeScriptCompilationOptions CreateTypeScriptCompilationOptions()
44+
{
45+
return new TypeScriptCompilationOptions
46+
{
47+
ToolPath = GetTypeScriptToolPath(),
48+
OutDir = ".output",
49+
RootDir = _host.ScriptConfig.RootScriptPath
50+
};
51+
}
52+
53+
private static string GetTypeScriptToolPath()
54+
{
55+
string path = ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.TypeScriptCompilerPath);
56+
if (path == null)
57+
{
58+
string basePath = Path.Combine(Environment.ExpandEnvironmentVariables("%programfiles(x86)%"), "Microsoft SDKs\\TypeScript");
59+
if (Directory.Exists(basePath))
60+
{
61+
path = Directory.GetDirectories(basePath)
62+
.OrderByDescending(d => d)
63+
.FirstOrDefault() ?? string.Empty;
64+
65+
path = Path.Combine(path, TypeScriptToolName);
66+
}
67+
}
68+
69+
return path ?? TypeScriptToolName;
70+
}
3071
}
3172
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using Microsoft.Azure.WebJobs.Script.Description.Node.TypeScript;
11+
using Microsoft.CodeAnalysis;
12+
13+
namespace Microsoft.Azure.WebJobs.Script.Description.Node.TypeScript
14+
{
15+
public interface ITypeScriptCompiler
16+
{
17+
Task<ImmutableArray<Diagnostic>> CompileAsync(string inputFile, TypeScriptCompilationOptions options);
18+
}
19+
}

src/WebJobs.Script/Description/Node/Compilation/TypeScript/TypeScriptCompilation.cs

Lines changed: 12 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,116 +4,39 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
7-
using System.Diagnostics;
87
using System.IO;
9-
using System.Linq;
10-
using System.Text.RegularExpressions;
118
using System.Threading;
129
using System.Threading.Tasks;
1310
using Microsoft.CodeAnalysis;
14-
using Microsoft.CodeAnalysis.Scripting;
15-
using Microsoft.CodeAnalysis.Text;
1611

1712
namespace Microsoft.Azure.WebJobs.Script.Description.Node.TypeScript
1813
{
1914
public class TypeScriptCompilation : IJavaScriptCompilation
2015
{
21-
private static readonly Regex DiagnosticRegex = new Regex("^(.*.ts)\\((\\d*),(\\d*)\\): (\\w*) (TS[0-9]{0,4}): (.*)$", RegexOptions.Compiled);
16+
private static readonly Lazy<ITypeScriptCompiler> _defaultCompiler = new Lazy<ITypeScriptCompiler>(() => new TypeScriptCompiler(), LazyThreadSafetyMode.ExecutionAndPublication);
17+
2218
private readonly string _inputFilePath;
2319
private readonly TypeScriptCompilationOptions _options;
2420
private readonly List<Diagnostic> _diagnostics = new List<Diagnostic>();
21+
private readonly ITypeScriptCompiler _compiler;
2522

26-
private TypeScriptCompilation(string inputFilePath, TypeScriptCompilationOptions options)
23+
private TypeScriptCompilation(string inputFilePath, TypeScriptCompilationOptions options, ITypeScriptCompiler compiler)
2724
{
25+
_compiler = compiler;
2826
_inputFilePath = inputFilePath;
2927
_options = options;
3028
}
3129

3230
public bool SupportsDiagnostics => true;
3331

34-
private Task CompileAsync()
32+
private async Task CompileAsync()
3533
{
36-
var tcs = new TaskCompletionSource<object>();
37-
38-
try
39-
{
40-
string inputDirectory = Path.GetDirectoryName(_inputFilePath);
41-
42-
var startInfo = new ProcessStartInfo
43-
{
44-
FileName = _options.ToolPath,
45-
RedirectStandardOutput = true,
46-
RedirectStandardError = true,
47-
CreateNoWindow = true,
48-
UseShellExecute = false,
49-
ErrorDialog = false,
50-
WorkingDirectory = inputDirectory,
51-
Arguments = _options.ToArgumentString(_inputFilePath)
52-
};
53-
54-
var process = new Process { StartInfo = startInfo };
55-
process.ErrorDataReceived += ProcessDataReceived;
56-
process.OutputDataReceived += ProcessDataReceived;
57-
process.EnableRaisingEvents = true;
58-
process.Exited += (s, e) =>
59-
{
60-
process.WaitForExit();
61-
process.Close();
62-
63-
if (_diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
64-
{
65-
tcs.SetException(new CompilationErrorException("Compilation failed.", GetDiagnostics()));
66-
}
67-
else
68-
{
69-
tcs.SetResult(null);
70-
}
71-
};
72-
73-
process.Start();
74-
75-
process.BeginErrorReadLine();
76-
process.BeginOutputReadLine();
77-
}
78-
catch (Exception exc)
79-
{
80-
tcs.SetException(exc);
81-
}
82-
83-
return tcs.Task;
84-
}
85-
86-
private void ProcessDataReceived(object sender, DataReceivedEventArgs e)
87-
{
88-
if (e.Data != null && TryParseDiagnostic(e.Data, out Diagnostic diagnostic))
89-
{
90-
_diagnostics.Add(diagnostic);
91-
}
92-
}
93-
94-
internal static bool TryParseDiagnostic(string data, out Diagnostic diagnostic)
95-
{
96-
diagnostic = null;
97-
Match match = DiagnosticRegex.Match(data);
98-
99-
if (match.Success)
100-
{
101-
DiagnosticSeverity severity = (DiagnosticSeverity)Enum.Parse(typeof(DiagnosticSeverity), match.Groups[4].Value, true);
102-
var descriptor = new DiagnosticDescriptor(match.Groups[5].Value, match.Groups[6].Value, match.Groups[6].Value, string.Empty, severity, true);
103-
int line = int.Parse(match.Groups[2].Value) - 1;
104-
int column = int.Parse(match.Groups[3].Value) - 1;
105-
var linePosition = new LinePosition(line, column);
106-
var location = Location.Create(match.Groups[1].Value, new TextSpan(linePosition.Line, 0), new LinePositionSpan(linePosition, linePosition));
107-
108-
diagnostic = Diagnostic.Create(descriptor, location);
109-
}
110-
111-
return diagnostic != null;
34+
var diagnostics = await _compiler.CompileAsync(_inputFilePath, _options);
11235
}
11336

114-
public static async Task<TypeScriptCompilation> CompileAsync(string inputFile, TypeScriptCompilationOptions options)
37+
public static async Task<TypeScriptCompilation> CompileAsync(string inputFile, TypeScriptCompilationOptions options, ITypeScriptCompiler compiler = null)
11538
{
116-
var compilation = new TypeScriptCompilation(inputFile, options);
39+
var compilation = new TypeScriptCompilation(inputFile, options, compiler ?? _defaultCompiler.Value);
11740
await compilation.CompileAsync();
11841

11942
return compilation;
@@ -125,10 +48,10 @@ public static async Task<TypeScriptCompilation> CompileAsync(string inputFile, T
12548

12649
public string Emit(CancellationToken cancellationToken)
12750
{
128-
string inputFilePath = Path.GetDirectoryName(_inputFilePath);
129-
string outputFileName = Path.GetFileNameWithoutExtension(_inputFilePath) + ".js";
51+
string relativeInputFilePath = FileUtility.GetRelativePath(_options.RootDir, _inputFilePath);
52+
string outputFileName = Path.ChangeExtension(relativeInputFilePath, ".js");
13053

131-
return Path.Combine(inputFilePath, _options.OutDir, outputFileName);
54+
return Path.Combine(Path.GetDirectoryName(_inputFilePath), _options.OutDir, outputFileName);
13255
}
13356
}
13457
}

src/WebJobs.Script/Description/Node/Compilation/TypeScript/TypeScriptCompilationOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
5+
using System.Diagnostics;
56
using System.Linq;
67
using System.Runtime.CompilerServices;
78
using System.Text;
89

910
namespace Microsoft.Azure.WebJobs.Script.Description.Node.TypeScript
1011
{
12+
[DebuggerDisplay("{DebuggerDisplay, nq}")]
1113
public class TypeScriptCompilationOptions
1214
{
1315
private readonly Dictionary<string, CompilationOption> _options = new Dictionary<string, CompilationOption>();
@@ -41,6 +43,14 @@ public string OutDir
4143
internal set => SetOption(value);
4244
}
4345

46+
public string RootDir
47+
{
48+
get => GetOption<StringCompilationOption>()?.Value;
49+
internal set => SetOption(value);
50+
}
51+
52+
private string DebuggerDisplay => ToArgumentString("<filename>");
53+
4454
private static string GetOptionName(string name)
4555
{
4656
return name?.ToLowerInvariant();

src/WebJobs.Script/Description/Node/Compilation/TypeScript/TypeScriptCompilationService.cs

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,11 @@ namespace Microsoft.Azure.WebJobs.Script.Description.Node.TypeScript
1212
{
1313
public class TypeScriptCompilationService : ICompilationService<IJavaScriptCompilation>
1414
{
15-
private const string DefaultToolName = "tsc.exe";
16-
private static readonly TypeScriptCompilationOptions DefaultCompilationOptions;
15+
private readonly TypeScriptCompilationOptions _compilationOptions;
1716

18-
static TypeScriptCompilationService()
17+
public TypeScriptCompilationService(TypeScriptCompilationOptions compilationOptions)
1918
{
20-
DefaultCompilationOptions = new TypeScriptCompilationOptions
21-
{
22-
ToolPath = GetToolPath(),
23-
OutDir = ".output"
24-
};
19+
_compilationOptions = compilationOptions;
2520
}
2621

2722
public string Language => "TypeScript";
@@ -30,31 +25,12 @@ static TypeScriptCompilationService()
3025

3126
public bool PersistsOutput => true;
3227

33-
private static string GetToolPath()
34-
{
35-
string path = ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.TypeScriptCompilerPath);
36-
if (path == null)
37-
{
38-
string basePath = Path.Combine(Environment.ExpandEnvironmentVariables("%programfiles(x86)%"), "Microsoft SDKs\\TypeScript");
39-
if (Directory.Exists(basePath))
40-
{
41-
path = Directory.GetDirectories(basePath)
42-
.OrderByDescending(d => d)
43-
.FirstOrDefault() ?? string.Empty;
44-
45-
path = Path.Combine(path, DefaultToolName);
46-
}
47-
}
48-
49-
return path ?? DefaultToolName;
50-
}
51-
5228
async Task<object> ICompilationService.GetFunctionCompilationAsync(FunctionMetadata functionMetadata)
5329
=> await GetFunctionCompilationAsync(functionMetadata);
5430

5531
public async Task<IJavaScriptCompilation> GetFunctionCompilationAsync(FunctionMetadata functionMetadata)
5632
{
57-
return await TypeScriptCompilation.CompileAsync(functionMetadata.ScriptFile, DefaultCompilationOptions);
33+
return await TypeScriptCompilation.CompileAsync(functionMetadata.ScriptFile, _compilationOptions);
5834
}
5935
}
6036
}

0 commit comments

Comments
 (0)