Skip to content

Commit f83c29d

Browse files
Luke Owlclaw2Luke Owlclaw2
authored andcommitted
- added support for importing external references (not module mode only)
- added ExceptionForUser for showing error message to user
1 parent 599887f commit f83c29d

5 files changed

Lines changed: 148 additions & 20 deletions

File tree

src/TypeScriptDefinitionGenerator/Generator/GenerationService.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,23 @@ public static string ConvertToTypeScript(ProjectItem sourceItem)
5858
Options.ReadOptionOverrides(sourceItem);
5959
VSHelpers.WriteOnOutputWindow(string.Format("{0} - Started", sourceItem.Name));
6060
var list = IntellisenseParser.ProcessFile(sourceItem);
61+
// path is needed for relative paths of imports
62+
var sourceItemPath = sourceItem.Properties.Item("FullPath").Value as string;
6163
VSHelpers.WriteOnOutputWindow(string.Format("{0} - Completed", sourceItem.Name));
62-
return IntellisenseWriter.WriteTypeScript(list);
64+
return IntellisenseWriter.WriteTypeScript(list, sourceItemPath);
6365
}
6466
catch (Exception ex)
6567
{
6668
VSHelpers.WriteOnOutputWindow(string.Format("{0} - Failure", sourceItem.Name));
67-
Telemetry.TrackException("ParseFailure", ex);
69+
if (ex is ExceptionForUser)
70+
{
71+
// "expected" exception, show to user instead of reporting
72+
VSHelpers.WriteOnOutputWindow(ex.Message);
73+
}
74+
else
75+
{
76+
Telemetry.TrackException("ParseFailure", ex);
77+
}
6878
return null;
6979
}
7080
}

src/TypeScriptDefinitionGenerator/Generator/IntellisenseWriter.cs

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Linq;
45
using System.Text;
56
using System.Text.RegularExpressions;
@@ -11,7 +12,7 @@ internal static class IntellisenseWriter
1112
{
1213
private static readonly Regex _whitespaceTrimmer = new Regex(@"^\s+|\s+$|\s*[\r\n]+\s*", RegexOptions.Compiled);
1314

14-
public static string WriteTypeScript(IEnumerable<IntellisenseObject> objects)
15+
public static string WriteTypeScript(IEnumerable<IntellisenseObject> objects, string sourceItemPath)
1516
{
1617
var sb = new StringBuilder();
1718
sb.AppendLine("// ------------------------------------------------------------------------------");
@@ -20,57 +21,112 @@ public static string WriteTypeScript(IEnumerable<IntellisenseObject> objects)
2021
sb.AppendLine("// </auto-generated>");
2122
sb.AppendLine("// ------------------------------------------------------------------------------");
2223

24+
string export = !Options.DeclareModule ? "export " : string.Empty;
25+
string prefixModule = Options.DeclareModule ? "\t" : String.Empty;
26+
27+
var sbBody = new StringBuilder();
28+
29+
var neededImports = new List<string>();
30+
2331
foreach (var ns in objects.GroupBy(o => o.Namespace))
2432
{
2533
if (Options.DeclareModule)
2634
{
27-
sb.AppendFormat("declare module {0} {{\r\n", ns.Key);
35+
sbBody.AppendFormat("declare module {0} {{\r\n", ns.Key);
2836
}
2937

30-
string export = !Options.DeclareModule ? "export " : string.Empty;
31-
string prefixModule = Options.DeclareModule ? "\t" : String.Empty;
32-
3338
foreach (IntellisenseObject io in ns)
3439
{
3540
if (!string.IsNullOrEmpty(io.Summary))
36-
sb.Append(prefixModule).AppendLine("/** " + _whitespaceTrimmer.Replace(io.Summary, "") + " */");
41+
sbBody.Append(prefixModule).AppendLine("/** " + _whitespaceTrimmer.Replace(io.Summary, "") + " */");
3742

3843
if (io.IsEnum)
3944
{
4045
string type = "enum ";
41-
sb.Append(prefixModule).Append(export).Append(type).Append(Utility.CamelCaseClassName(io.Name)).Append(" ");
46+
sbBody.Append(prefixModule).Append(export).Append(type).Append(Utility.CamelCaseClassName(io.Name)).Append(" ");
4247

43-
sb.AppendLine("{");
44-
WriteTSEnumDefinition(sb, prefixModule + "\t", io.Properties);
45-
sb.Append(prefixModule).AppendLine("}");
48+
sbBody.AppendLine("{");
49+
WriteTSEnumDefinition(sbBody, prefixModule + "\t", io.Properties);
50+
sbBody.Append(prefixModule).AppendLine("}");
4651
}
4752
else
4853
{
4954
string type = Options.ClassInsteadOfInterface ? "class " : "interface ";
50-
sb.Append(prefixModule).Append(export).Append(type).Append(Utility.CamelCaseClassName(io.Name)).Append(" ");
55+
sbBody.Append(prefixModule).Append(export).Append(type).Append(Utility.CamelCaseClassName(io.Name)).Append(" ");
5156

5257
if (!string.IsNullOrEmpty(io.BaseName))
5358
{
54-
sb.Append("extends ");
59+
sbBody.Append("extends ");
5560

5661
if (!string.IsNullOrEmpty(io.BaseNamespace) && io.BaseNamespace != io.Namespace)
57-
sb.Append(io.BaseNamespace).Append(".");
62+
sbBody.Append(io.BaseNamespace).Append(".");
5863

59-
sb.Append(Utility.CamelCaseClassName(io.BaseName)).Append(" ");
64+
sbBody.Append(Utility.CamelCaseClassName(io.BaseName)).Append(" ");
6065
}
6166

62-
sb.AppendLine("{");
63-
WriteTSInterfaceDefinition(sb, prefixModule + "\t", io.Properties);
64-
sb.Append(prefixModule).AppendLine("}");
67+
sbBody.AppendLine("{");
68+
WriteTSInterfaceDefinition(sbBody, prefixModule + "\t", io.Properties);
69+
sbBody.Append(prefixModule).AppendLine("}");
70+
// remember client-side references for which we need imports
71+
neededImports.AddRange(io.Properties.Where(p => p.Type.ClientSideReferenceName != null)
72+
.Select(p => p.Type.ClientSideReferenceName));
6573
}
6674
}
6775

6876
if (Options.DeclareModule)
6977
{
70-
sb.AppendLine("}");
78+
sbBody.AppendLine("}");
7179
}
7280
}
7381

82+
// if interface, import external interfaces and base classes
83+
if (!Options.DeclareModule)
84+
{
85+
var imports = new List<string>();
86+
87+
var references = objects.SelectMany(o => o.References).Distinct();
88+
foreach (var reference in references)
89+
{
90+
var referencePathRelative = Utility.GetRelativePath(sourceItemPath, reference);
91+
// remove trailing ".ts" which is not expected for TS imports
92+
referencePathRelative = referencePathRelative.Substring(0, referencePathRelative.Length - 3);
93+
// make sure path contains forward slashes which are expected by TS
94+
referencePathRelative = referencePathRelative.Replace(Path.DirectorySeparatorChar, '/');
95+
var referenceName = Utility.RemoveDefaultExtension(Path.GetFileName(reference));
96+
97+
// skipped indirect references
98+
if (!neededImports.Contains(referenceName))
99+
{
100+
continue;
101+
}
102+
103+
sb.AppendLine($"import {{ {referenceName} }} from \"{referencePathRelative}\";");
104+
imports.Add(referenceName);
105+
}
106+
107+
// also import base classes if not yet imported
108+
var baseClasses = objects.Select(o => o.BaseName).Where(b => b != null && !imports.Contains(b)).Distinct();
109+
foreach (var b in baseClasses)
110+
{
111+
var expectedBaseClassPath = Path.Combine(Path.GetDirectoryName(sourceItemPath), b + ".cs");
112+
if (!File.Exists(expectedBaseClassPath))
113+
{
114+
throw new ExceptionForUser($"Could not find base class for {b}. Expected path: {expectedBaseClassPath}");
115+
}
116+
117+
sb.AppendLine($"import {{ {b} }} from \"./{b}.generated\";");
118+
imports.Add(b);
119+
}
120+
121+
var notImportedNeededImports = neededImports.Except(imports);
122+
if (notImportedNeededImports.Any())
123+
{
124+
throw new ExceptionForUser($"Sorry, needed imports missing: {string.Join(", ", notImportedNeededImports)}");
125+
}
126+
}
127+
128+
sb.Append(sbBody);
129+
74130
if (Options.EOLType == EOLType.LF)
75131
sb.Replace("\r\n", "\n");
76132

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace TypeScriptDefinitionGenerator.Helpers
8+
{
9+
/// <summary>
10+
/// Exception which contains message which can be shown to user.
11+
/// </summary>
12+
class ExceptionForUser : Exception
13+
{
14+
public ExceptionForUser(string message) : base(message) { }
15+
public ExceptionForUser(string message, Exception inner) : base(message, inner) { }
16+
}
17+
}

src/TypeScriptDefinitionGenerator/Helpers/Utility.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ public static string GenerateFileName(string sourceFile)
1010
return Path.ChangeExtension(sourceFile, GetDefaultExtension(Path.GetExtension(sourceFile)));
1111
}
1212

13+
public static string RemoveDefaultExtension(string tsFile)
14+
{
15+
var defaultExt = GetDefaultExtension(string.Empty);
16+
if (!tsFile.EndsWith(defaultExt))
17+
{
18+
throw new System.ArgumentException("File must end with default extension");
19+
}
20+
21+
return tsFile.Substring(0, tsFile.Length - defaultExt.Length);
22+
}
23+
24+
1325
public static string GetDefaultExtension(string originalExt)
1426
{
1527
string declaredExt = Options.DeclareModule ? ".d" : string.Empty;
@@ -57,5 +69,37 @@ private static string CamelCase(string name)
5769
}
5870
return name[0].ToString(CultureInfo.CurrentCulture).ToLower(CultureInfo.CurrentCulture) + name.Substring(1);
5971
}
72+
73+
/// <summary>
74+
/// Get relative path of <paramref name="toPath"/> relative to <paramref name="fromPath"/>.
75+
/// </summary>
76+
/// <param name="fromPath">Base path</param>
77+
/// <param name="toPath">File to be reference relatively</param>
78+
/// <returns></returns>
79+
/// <remarks>Source: https://stackoverflow.com/questions/275689/how-to-get-relative-path-from-absolute-path#answer-485516</remarks>
80+
public static string GetRelativePath(string fromPath, string toPath)
81+
{
82+
int fromAttr = Directory.Exists(fromPath) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
83+
int toAttr = Directory.Exists(toPath) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
84+
85+
var path = new System.Text.StringBuilder(260); // MAX_PATH
86+
if (PathRelativePathTo(
87+
path,
88+
fromPath,
89+
fromAttr,
90+
toPath,
91+
toAttr) == 0)
92+
{
93+
throw new System.ArgumentException("Paths must have a common prefix");
94+
}
95+
return path.ToString();
96+
}
97+
98+
private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
99+
private const int FILE_ATTRIBUTE_NORMAL = 0x80;
100+
101+
[System.Runtime.InteropServices.DllImport("shlwapi.dll", SetLastError = true)]
102+
private static extern int PathRelativePathTo(System.Text.StringBuilder pszPath,
103+
string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);
60104
}
61105
}

src/TypeScriptDefinitionGenerator/TypeScriptDefinitionGenerator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<Compile Include="Generator\GenerationService.cs" />
5959
<Compile Include="Generator\DtsGenerator.cs" />
6060
<Compile Include="DtsPackage.cs" />
61+
<Compile Include="Helpers\ExceptionForUser.cs" />
6162
<Compile Include="Helpers\Telemetry.cs" />
6263
<Compile Include="Helpers\Utility.cs" />
6364
<Compile Include="Helpers\VSHelpers.cs" />

0 commit comments

Comments
 (0)