using System.Reflection;
using System.IO;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using System.Linq;
using CSharpToJavaScript.Utils;
namespace CSharpToJavaScript;
///
/// Main type for CSharpToJavaScript.
///
public static class CSTOJS
{
///
/// This method translates CSharp string into JavaScript with specified options.
///
/// . CS string with options.
/// Optional. Array of references.
///
public static FileData Translate(FileData file, MetadataReference[]? references = null)
{
FileData[] files = Translate([file], references);
return files[0];
}
///
/// This method translates CSharp string into JavaScript with specified options.
///
/// Array of . CS strings with options.
/// Optional. Array of references.
/// Array of . JS strings
public static FileData[] Translate(FileData[] files, MetadataReference[]? references = null)
{
Assembly assembly = Assembly.GetExecutingAssembly();
//https://stackoverflow.com/a/73474279
Log.InfoLine($"{assembly.GetName().Name} {assembly.GetCustomAttribute()?.InformationalVersion}");
if (references == null)
references = GetReferences(files[0].OptionsForFile);
SyntaxTree[] trees = new SyntaxTree[files.Length];
for (int i = 0; i < files.Length; i++)
{
trees[i] = CSharpSyntaxTree.ParseText(files[i].SourceStr);
}
trees[0] = AddGlobalUsings(trees[0]);
for (int i = 0; i < files.Length; i++)
{
if (files[i].OptionsForFile.TranslateFile == false)
continue;
if (files[i].OptionsForFile.NormalizeWhitespace)
trees[i] = trees[i].GetRoot().NormalizeWhitespace().SyntaxTree;
if (files[i].OptionsForFile.KeepBraceOnTheSameLine)
{
//Mostly deleted whitespaces, still TODO?
SyntaxToken[] allBraces = trees[i].GetRoot().DescendantTokens().Where((e) => e.IsKind(SyntaxKind.OpenBraceToken)).ToArray();
List allTriviaToDelete = new();
for (int j = 0; j < allBraces.Length; j++)
{
if (allBraces[j].HasLeadingTrivia)
{
SyntaxTriviaList _lt = allBraces[j].LeadingTrivia;
for (int y = 0; y < _lt.Count; y++)
{
allTriviaToDelete.Add(_lt[y]);
}
}
}
//Is this the right way to delete trivia?
trees[i] = trees[i].GetRoot().ReplaceTrivia(allTriviaToDelete, (o, r) => SyntaxFactory.ElasticMarker).SyntaxTree;
allBraces = trees[i].GetRoot().DescendantTokens().Where((e) => e.IsKind(SyntaxKind.OpenBraceToken)).ToArray();
List allTriviaToReplace = new();
for (int j = 0; j < allBraces.Length; j++)
{
SyntaxToken _token = allBraces[j].GetPreviousToken();
if (_token.HasTrailingTrivia)
{
SyntaxTrivia _trivia = _token.TrailingTrivia.Where((e) => e.IsKind(SyntaxKind.EndOfLineTrivia)).FirstOrDefault();
if (!_trivia.IsKind(SyntaxKind.None))
{
allTriviaToReplace.Add(_trivia);
}
}
}
trees[i] = trees[i].GetRoot().ReplaceTrivia(allTriviaToReplace, (o, r) => SyntaxFactory.Space).SyntaxTree;
}
}
CSharpCompilation compilation = CSharpCompilation
.Create("HelloWorld")
.AddReferences(references)
.AddSyntaxTrees(trees);
for (int i = 0; i < files.Length; i++)
{
if (files[i].OptionsForFile.TranslateFile == false)
continue;
Walker _walker = new(files[i].OptionsForFile, compilation.GetSemanticModel(trees[i]));
_walker.JSSB.Append(files[i].OptionsForFile.AddSBAtTheTop);
_walker.Visit(trees[i].GetRoot());
_walker.JSSB.Append(files[i].OptionsForFile.AddSBAtTheBottom);
files[i].TranslatedStr = _walker.JSSB.ToString();
}
return files;
}
private static MetadataReference[] GetReferences(CSTOJSOptions options)
{
HashSet assemblyMetadata = new();
Assembly? assembly = Assembly.GetEntryAssembly();
AssemblyName[] assemblyNames = assembly?.GetReferencedAssemblies() ?? [];
string? assemblyPath = Path.GetDirectoryName(assembly?.Location);
string? objectAssemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
string? binPath = Directory.Exists("./bin/") ? "./bin/" : null;
if (assemblyNames.Length > 0)
{
for (int j = 0; j < assemblyNames.Length; j++)
{
if (assemblyPath != null && File.Exists(Path.Combine(assemblyPath, assemblyNames[j].Name + ".dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, assemblyNames[j].Name + ".dll")));
if (objectAssemblyPath != null && File.Exists(Path.Combine(objectAssemblyPath, assemblyNames[j].Name + ".dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, assemblyNames[j].Name + ".dll")));
}
}
if (assemblyPath != null)
{
if (File.Exists(Path.Combine(assemblyPath, "CSharpToJavaScript.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "CSharpToJavaScript.dll")));
}
if (objectAssemblyPath != null)
{
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Private.CoreLib.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Private.CoreLib.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Collections.Generics.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Collections.Generics.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.IO.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.IO.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Linq.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Linq.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Net.Http.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Net.Http.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Threading.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Threading.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Threading.Tasks.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Threading.Tasks.dll")));
if (File.Exists(Path.Combine(objectAssemblyPath, "System.Console.dll")))
assemblyMetadata.Add(MetadataReference.CreateFromFile(Path.Combine(objectAssemblyPath, "System.Console.dll")));
}
if (binPath != null)
{
string[] files = Directory.GetFiles(binPath, "*.dll", SearchOption.AllDirectories);
for (int j = 0; j < files.Length; j++)
{
assemblyMetadata.Add(MetadataReference.CreateFromFile(files[j]));
}
}
if (options.Debug)
{
Log.InfoLine($"+++");
Log.InfoLine($"assemblyPath: {assemblyPath}");
Log.InfoLine($"objectAssemblyPath: {objectAssemblyPath}");
Log.InfoLine($"binPath: {binPath}");
foreach (MetadataReference metadata in assemblyMetadata)
{
Log.WriteLine(metadata.Display ?? "null display string");
}
Log.InfoLine($"---");
}
MetadataReference[] references = new MetadataReference[assemblyMetadata.Count];
int i = 0;
foreach (MetadataReference metadata in assemblyMetadata)
{
references[i] = metadata;
i++;
}
if (options.Debug)
{
for (int j = 0; j < references.Length; j++)
{
Log.WriteLine(references[j].Display ?? "null display string");
}
Log.InfoLine($"+++");
}
return references;
}
private static SyntaxTree AddGlobalUsings(SyntaxTree tree)
{
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
UsingDirectiveSyntax[] oldUsing = root.Usings.ToArray();
//https://stackoverflow.com/a/72938702
root = root.WithUsings
(
SyntaxFactory.List
(
new UsingDirectiveSyntax[]
{
SyntaxFactory.UsingDirective
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.UsingDirective
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
),
SyntaxFactory.IdentifierName("Collections")
),
SyntaxFactory.IdentifierName("Generic")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.UsingDirective
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
),
SyntaxFactory.IdentifierName("IO")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.UsingDirective
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
),
SyntaxFactory.IdentifierName("Linq")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.UsingDirective
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
),
SyntaxFactory.IdentifierName("Net")
),
SyntaxFactory.IdentifierName("Http")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.UsingDirective
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
),
SyntaxFactory.IdentifierName("Threading")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.UsingDirective
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.QualifiedName
(
SyntaxFactory.AliasQualifiedName
(
SyntaxFactory.IdentifierName
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
),
SyntaxFactory.IdentifierName("System")
),
SyntaxFactory.IdentifierName("Threading")
),
SyntaxFactory.IdentifierName("Tasks")
)
)
.WithGlobalKeyword
(
SyntaxFactory.Token(SyntaxKind.GlobalKeyword)
)
}
)
).AddUsings(oldUsing);
return root.SyntaxTree;
}
}
///
/// FileData includes a CSharp string, options and a JavaScript string.
///
public class FileData
{
//Delete this?
public string PathID { get; set; } = string.Empty;
///
/// Options for a translation.
///
public CSTOJSOptions OptionsForFile { get; set; } = new();
///
/// CS input string.
///
public string SourceStr { get; set; } = string.Empty;
///
/// JS translated string.
///
public string TranslatedStr { get; set; } = string.Empty;
}