11using System ;
22using System . Collections . Generic ;
3+ using System . IO ;
34using System . Linq ;
45using System . Text ;
56using System . Text . RegularExpressions ;
@@ -11,7 +12,13 @@ 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+ /// <summary>
16+ /// Generates TypeScript file for given C# class/enum (IntellisenseObject).
17+ /// </summary>
18+ /// <param name="objects">IntellisenseObject of class/enum</param>
19+ /// <param name="sourceItemPath">Path to C# source file</param>
20+ /// <returns>TypeScript file content as string</returns>
21+ public static string WriteTypeScript ( IList < IntellisenseObject > objects , string sourceItemPath )
1522 {
1623 var sb = new StringBuilder ( ) ;
1724 sb . AppendLine ( "// ------------------------------------------------------------------------------" ) ;
@@ -20,57 +27,118 @@ public static string WriteTypeScript(IEnumerable<IntellisenseObject> objects)
2027 sb . AppendLine ( "// </auto-generated>" ) ;
2128 sb . AppendLine ( "// ------------------------------------------------------------------------------" ) ;
2229
30+ string export = ! Options . DeclareModule ? "export " : string . Empty ;
31+ string prefixModule = Options . DeclareModule ? "\t " : string . Empty ;
32+
33+ var sbBody = new StringBuilder ( ) ;
34+
35+ var neededImports = new List < string > ( ) ;
36+
2337 foreach ( var ns in objects . GroupBy ( o => o . Namespace ) )
2438 {
2539 if ( Options . DeclareModule )
2640 {
27- sb . AppendFormat ( "declare module {0} {{\r \n " , ns . Key ) ;
41+ sbBody . AppendFormat ( "declare module {0} {{\r \n " , ns . Key ) ;
2842 }
2943
30- string export = ! Options . DeclareModule ? "export " : string . Empty ;
31- string prefixModule = Options . DeclareModule ? "\t " : String . Empty ;
32-
3344 foreach ( IntellisenseObject io in ns )
3445 {
3546 if ( ! string . IsNullOrEmpty ( io . Summary ) )
36- sb . Append ( prefixModule ) . AppendLine ( "/** " + _whitespaceTrimmer . Replace ( io . Summary , "" ) + " */" ) ;
47+ sbBody . Append ( prefixModule ) . AppendLine ( "/** " + _whitespaceTrimmer . Replace ( io . Summary , "" ) + " */" ) ;
3748
3849 if ( io . IsEnum )
3950 {
4051 string type = "enum " ;
41- sb . Append ( prefixModule ) . Append ( export ) . Append ( type ) . Append ( Utility . CamelCaseClassName ( io . Name ) ) . Append ( " " ) ;
52+ sbBody . Append ( prefixModule ) . Append ( export ) . Append ( type ) . Append ( Utility . CamelCaseClassName ( io . Name ) ) . Append ( " " ) ;
4253
43- sb . AppendLine ( "{" ) ;
44- WriteTSEnumDefinition ( sb , prefixModule + "\t " , io . Properties ) ;
45- sb . Append ( prefixModule ) . AppendLine ( "}" ) ;
54+ sbBody . AppendLine ( "{" ) ;
55+ WriteTSEnumDefinition ( sbBody , prefixModule + "\t " , io . Properties ) ;
56+ sbBody . Append ( prefixModule ) . AppendLine ( "}" ) ;
4657 }
4758 else
4859 {
4960 string type = Options . ClassInsteadOfInterface ? "class " : "interface " ;
50- sb . Append ( prefixModule ) . Append ( export ) . Append ( type ) . Append ( Utility . CamelCaseClassName ( io . Name ) ) . Append ( " " ) ;
61+ sbBody . Append ( prefixModule ) . Append ( export ) . Append ( type ) . Append ( Utility . CamelCaseClassName ( io . Name ) ) . Append ( " " ) ;
5162
5263 if ( ! string . IsNullOrEmpty ( io . BaseName ) )
5364 {
54- sb . Append ( "extends " ) ;
65+ sbBody . Append ( "extends " ) ;
5566
5667 if ( ! string . IsNullOrEmpty ( io . BaseNamespace ) && io . BaseNamespace != io . Namespace )
57- sb . Append ( io . BaseNamespace ) . Append ( "." ) ;
68+ sbBody . Append ( io . BaseNamespace ) . Append ( "." ) ;
5869
59- sb . Append ( Utility . CamelCaseClassName ( io . BaseName ) ) . Append ( " " ) ;
70+ sbBody . Append ( Utility . CamelCaseClassName ( io . BaseName ) ) . Append ( " " ) ;
6071 }
6172
62- sb . AppendLine ( "{" ) ;
63- WriteTSInterfaceDefinition ( sb , prefixModule + "\t " , io . Properties ) ;
64- sb . Append ( prefixModule ) . AppendLine ( "}" ) ;
73+ sbBody . AppendLine ( "{" ) ;
74+ WriteTSInterfaceDefinition ( sbBody , prefixModule + "\t " , io . Properties ) ;
75+ sbBody . Append ( prefixModule ) . AppendLine ( "}" ) ;
76+ // Remember client-side references for which we need imports.
77+ // Dictionary are built-in into TS, they need no imports.
78+ neededImports . AddRange ( io . Properties . Where ( p => p . Type . ClientSideReferenceName != null &&
79+ ! p . Type . IsDictionary ) . Select ( p => p . Type . ClientSideReferenceName ) ) ;
6580 }
6681 }
6782
6883 if ( Options . DeclareModule )
6984 {
70- sb . AppendLine ( "}" ) ;
85+ sbBody . AppendLine ( "}" ) ;
86+ }
87+ }
88+
89+ // if interface, import external interfaces and base classes
90+ if ( ! Options . DeclareModule )
91+ {
92+ var imports = new List < string > ( ) ;
93+
94+ var references = objects . SelectMany ( o => o . References ) . Distinct ( ) ;
95+ foreach ( var reference in references )
96+ {
97+ var referencePathRelative = Utility . GetRelativePath ( sourceItemPath , reference ) ;
98+ // remove trailing ".ts" which is not expected for TS imports
99+ referencePathRelative = referencePathRelative . Substring ( 0 , referencePathRelative . Length - 3 ) ;
100+ // make sure path contains forward slashes which are expected by TS
101+ referencePathRelative = referencePathRelative . Replace ( Path . DirectorySeparatorChar , '/' ) ;
102+ var referenceName = Utility . RemoveDefaultExtension ( Path . GetFileName ( reference ) ) ;
103+
104+ // skipped indirect references
105+ if ( ! neededImports . Contains ( referenceName ) )
106+ {
107+ continue ;
108+ }
109+
110+ sb . AppendLine ( $ "import {{ { referenceName } }} from \" { referencePathRelative } \" ;") ;
111+ imports . Add ( referenceName ) ;
112+ }
113+
114+ // also import base classes if not yet imported
115+ var baseClasses = objects . Select ( o => o . BaseName ) . Where ( b => b != null && ! imports . Contains ( b ) ) . Distinct ( ) ;
116+ foreach ( var b in baseClasses )
117+ {
118+ var expectedBaseClassPath = Path . Combine ( Path . GetDirectoryName ( sourceItemPath ) , b + ".cs" ) ;
119+ if ( ! File . Exists ( expectedBaseClassPath ) )
120+ {
121+ throw new ExceptionForUser ( $ "Could not find base class for { b } . Expected path: { expectedBaseClassPath } ") ;
122+ }
123+
124+ sb . AppendLine ( $ "import {{ { b } }} from \" ./{ b } .generated\" ;") ;
125+ imports . Add ( b ) ;
126+ }
127+
128+ var notImportedNeededImports = neededImports . Except ( imports ) . ToList ( ) ;
129+ if ( notImportedNeededImports . Any ( ) )
130+ {
131+ var exceptionForDeveloper =
132+ $ "Sorry, needed imports missing: { string . Join ( ", " , notImportedNeededImports ) } . " +
133+ $ "Make sure file names match contained class/enum name.";
134+ sb . AppendLine ( $ "// { exceptionForDeveloper } ") ;
135+ VSHelpers . WriteOnOutputWindow ( exceptionForDeveloper ) ;
136+ //throw new ExceptionForUser(exceptionForDeveloper);
71137 }
72138 }
73139
140+ sb . Append ( sbBody ) ;
141+
74142 if ( Options . EOLType == EOLType . LF )
75143 sb . Replace ( "\r \n " , "\n " ) ;
76144
@@ -88,7 +156,7 @@ private static string CleanEnumInitValue(string value)
88156 if ( trimedValue . Length > 0 ) return trimedValue ;
89157 return "0" ;
90158 }
91-
159+
92160 private static void WriteTypeScriptComment ( IntellisenseProperty p , StringBuilder sb , string prefix )
93161 {
94162 if ( string . IsNullOrEmpty ( p . Summary ) ) return ;
0 commit comments