See More

using System.Reflection; using System.IO; using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis; using CSharpToJavaScript.Utils; using System.Collections.Immutable; 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, path: files[i].FileName); } trees[0] = AddGlobalUsings(trees[0]).WithFilePath(files[0].FileName); CSharpCompilation compilation = CSharpCompilation .Create("HelloWorld") .AddReferences(references) .AddSyntaxTrees(trees); Dictionary> exportedClasses = new(); //Enable modules if, more than 2 files. if (files.Length >= 2) { for (int i = 0; i < files.Length; i++) { if (files[i].OptionsForFile.EnableModules == 1) files[i].OptionsForFile.EnableModules = 2; } } for (int i = 0; i < files.Length; i++) { if (files[i].OptionsForFile.EnableModules >= 2) { SemanticModel _model = compilation.GetSemanticModel(trees[i]); ExportClassesWalker _exportClassesWalker = new(_model, ref exportedClasses); SyntaxNode _root = trees[i].GetRoot(); _exportClassesWalker.Visit(_root); } } for (int i = 0; i < files.Length; i++) { if (files[i].OptionsForFile.TranslateFile == false) continue; SemanticModel _model = compilation.GetSemanticModel(trees[i]); if (files[i].OptionsForFile.DisableDiagnostics == false) { ImmutableArray diagnostics = _model.GetDiagnostics(); for (int j = 0; j < diagnostics.Length; j++) { if (diagnostics[j].Severity == DiagnosticSeverity.Hidden && files[i].OptionsForFile.Debug == true) Log.WriteLine(diagnostics[j].ToString()); else if (diagnostics[j].Severity == DiagnosticSeverity.Info && files[i].OptionsForFile.Debug == true) Log.InfoLine(diagnostics[j].ToString()); else if (diagnostics[j].Severity == DiagnosticSeverity.Warning) Log.WarningLine(diagnostics[i].ToString()); else if (diagnostics[j].Severity == DiagnosticSeverity.Error) Log.ErrorLine(diagnostics[i].ToString()); } } SyntaxNode _root = trees[i].GetRoot(); WithSemanticWalker _withSemanticWalker = new(_model, files[i].OptionsForFile, exportedClasses); WithoutSemanticRewriter _withoutSemanticRewriter = new(files[i].OptionsForFile); StringBuilderWalker _stringBuilderWalker = new(); _withSemanticWalker.Visit(_root); _root = _root.ReplaceNodes(_withSemanticWalker.ReplaceNodes.Keys, (o, r) => { return _withSemanticWalker.ReplaceNodes[o]; }); if (files[i].OptionsForFile.Debug) files[i].DebugStrings[0] = _root.ToFullString(); _root = _withoutSemanticRewriter.Visit(_root); if (files[i].OptionsForFile.Debug) files[i].DebugStrings[1] = _root.ToFullString(); if (files[i].OptionsForFile.NormalizeWhitespace) _root = _root.NormalizeWhitespace(); if (files[i].OptionsForFile.KeepBraceOnTheSameLine) { KeepBraceOnTheSameLineRewriter _keepBraceOnTheSameLineRewriter = new(); _root = _keepBraceOnTheSameLineRewriter.Visit(_root); } _stringBuilderWalker.JSSB.Append(files[i].OptionsForFile.AddSBAtTheTop); //Import modules if (files[i].OptionsForFile.EnableModules >= 2) { Dictionary>.KeyCollection _keys = _withSemanticWalker.ImportClasses.Keys; foreach (string _filename in _keys) { if (_stringBuilderWalker.JSSB.Length != 0) { if (_stringBuilderWalker.JSSB[_stringBuilderWalker.JSSB.Length - 1] != '\n') _stringBuilderWalker.JSSB.AppendLine(); } _stringBuilderWalker.JSSB.Append("import { "); for (int j = 0; j < _withSemanticWalker.ImportClasses[_filename].Count; j++) { if (j == _withSemanticWalker.ImportClasses[_filename].Count - 1) _stringBuilderWalker.JSSB.Append($"{_withSemanticWalker.ImportClasses[_filename][j]}"); else _stringBuilderWalker.JSSB.Append($"{_withSemanticWalker.ImportClasses[_filename][j]}, "); } _stringBuilderWalker.JSSB.AppendLine($" }} from './{_filename}';"); } } _stringBuilderWalker.Visit(_root); //Export modules if (files[i].OptionsForFile.EnableModules >= 2) { if (exportedClasses.TryGetValue(files[i].FileName, out List? _value)) { if (_stringBuilderWalker.JSSB[_stringBuilderWalker.JSSB.Length - 1] != '\n') _stringBuilderWalker.JSSB.AppendLine(); _stringBuilderWalker.JSSB.Append("export { "); for (int j = 0; j < _value.Count; j++) { if (j == _value.Count - 1) _stringBuilderWalker.JSSB.Append($"{_value[j]}"); else _stringBuilderWalker.JSSB.Append($"{_value[j]}, "); } _stringBuilderWalker.JSSB.Append(" };"); } } _stringBuilderWalker.JSSB.Append(files[i].OptionsForFile.AddSBAtTheBottom); files[i].TranslatedStr = _stringBuilderWalker.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? customPath = Directory.Exists(options.CustomPathToDLLs) ? options.CustomPathToDLLs : 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 (customPath != null) { string[] files = Directory.GetFiles(customPath, "*.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($"customPath: '{customPath}'"); foreach (MetadataReference metadata in assemblyMetadata) { Log.WriteLine(metadata.Display ?? "null display string"); } Log.InfoLine($"assemblyMetadata count: '{assemblyMetadata.Count}'"); 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($"references length: '{references.Length}'"); Log.InfoLine($"+++"); } return references; } private static SyntaxTree AddGlobalUsings(SyntaxTree tree) { CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); SyntaxList newUsing = 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) ) } ).AddRange(root.Usings); //https://stackoverflow.com/a/72938702 root = root.WithUsings(newUsing); return root.SyntaxTree; } } /// /// FileData includes a CSharp string, options, and a JavaScript string. /// public class FileData { /// /// Full JS filename. Needed for modules. /// public string FileName { 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; /// /// Debug strings. /// 0: WithSemanticWalker /// 1: WithoutSemanticRewriter /// public string[] DebugStrings { get; set; } = [string.Empty, string.Empty]; }