static public TSType NewType(XElement elem, TypeScriptDefContext context) { if (elem == null) return null; TSType T = null; UnionTypeModel TypeList = new UnionTypeModel(); HashSet<string> typesAlreadyInTheList = new HashSet<string>(); foreach (XElement e in elem.Elements()) { switch (e.Name.LocalName) { case "dotident": T = new BasicType(e); if (!Tool.IsBasicType(T)) T = (TSType)TypeDeclaration.New(e, null, context) ?? T; break; case "anonymoustype": T = new AnonymousType(e, context); break; case "functiontype": T = new FunctionType(e, context); break; case "generic": T = new Generic(e, context); break; case "arraylevel": T = new Model.Array(T); TypeList.Types.RemoveAt(TypeList.Types.Count - 1); break; default: continue; } if (!typesAlreadyInTheList.Contains(T.ToString())) // Avoids adding the same type multiple times { TypeList.Types.Add(T); typesAlreadyInTheList.Add(T.ToString()); } } if (TypeList.Count < 2) return T; else return TypeList; }
static string PARSER_VERSION = "0001"; // Increase this value when you want to force-regenerate the files in the end-users solutions. This is useful when we release a breaking change and we want the end-user to force-recompile the TypeScript Definition files. // Transform every TypeScript Definition file into XML and then into C# Wrapper public static string ProcessTypeScriptDefFile(string InputFiles, string OutputDirectory, ILogger Logger) { #if GENERATE_UNION_TYPES // Use that if you want to regenerate the UnionType.cs file // Recompile a project that use TypeScript Definition file // Go to the output and copy/paste the block // Then just use a small regex to replace like this: change '^>1 ' to '' (don't forget regex mode) Logger.WriteMessage(Tool.GenerateUnionType(6)); #endif //-------------------------------------------------------- // PREPARE FOR PARSING //-------------------------------------------------------- // Create the dictionary that will be used to communicate with the Progress Dialogs: Dictionary <string, string> typeScriptFileNameToProgress = new Dictionary <string, string>(); // The key is the TypeScript file name, and the value is the progress to display. We set the progress to "DONE" when we want to close the window. // Create the context TypeScriptDefContext context = new TypeScriptDefContext() { OutputDirectory = OutputDirectory, Logger = Logger }; // Reset AnonymousTypes static properties AnonymousType.Anons = new List <AnonymousType>(); AnonymousType.AnonID = 1; // Create and load the Infos XML file TypeScriptDefToCSharpOutput DTSOld; TypeScriptDefToCSharpOutput DTSNew = new TypeScriptDefToCSharpOutput(); if (File.Exists(OutputDirectory + Constants.NAME_OF_TYPESCRIPT_DEFINITIONS_CACHE_FILE)) { string text = File.ReadAllText(OutputDirectory + Constants.NAME_OF_TYPESCRIPT_DEFINITIONS_CACHE_FILE); DTSOld = Tool.Deserialize <TypeScriptDefToCSharpOutput>(text); } else { DTSOld = new TypeScriptDefToCSharpOutput(); } // Store an array with every .d.ts files string[] args = InputFiles.Split(';'); //-------------------------------------------------------- // PARSING OF TYPESCRIPT DEFINITION FILES (.d.ts) //-------------------------------------------------------- // Convert every TypeScript Definition file into an XML file // We need to add a check with a hash to know if there is already the XML file for (int i = 0; i < args.Length; i++) { // Remove the ".d.ts" part of the name args[i] = args[i].Remove(args[i].Length - 5); // Read the file string TSDefinitionOriginal = File.ReadAllText(args[i] + ".d.ts"); // Remove the comments string TSDefinition = ClearComments(TSDefinitionOriginal); // Calculate the file's hash string fileHash = Tool.GetHashString(TSDefinition) + PARSER_VERSION; // See note near the "PARSER_VERSION" declaration. // Try to remove the file from the Old Infos (null if not found) var file = DTSOld.ExtractFileByName(args[i] + ".d.ts"); //------- PARSE THE FILE ONLY IF NEEDED ------- // If there is no file with that name, or a different hash, or no corresponding XML if (file == null || file.FileContentHash != fileHash || File.Exists(OutputDirectory + args[i] + ".xml") == false) { string fileName = args[i] + ".d.ts"; Logger.WriteMessage("Parsing file: " + fileName); // Display the Progress Dialog (on UI thread): typeScriptFileNameToProgress[fileName] = "Processing " + fileName; ProgressDialog.ShowOnUIThread(fileName, typeScriptFileNameToProgress); Action <string> methodToUpdateProgress = (text => { typeScriptFileNameToProgress[fileName] = "processing token '" + (text ?? "") + "' in file '" + fileName + "'"; }); // Add this file to the New Infos DTSNew.TypeScriptDefinitionFiles.Add(new TypeScriptDefinitionFile() { FileName = args[i] + ".d.ts", FileContentHash = fileHash }); //------- PARSING ------- // Use TinyPG generated classes to parse the file Scanner Scanner = new Scanner(methodToUpdateProgress); Parser Parser = new Parser(Scanner); ParseTree Tree = Parser.Parse(TSDefinition); // Hide the Progress Dialog (this variable is watched by the other thread): typeScriptFileNameToProgress[fileName] = ProgressDialog.Done; // HOW TO DEBUG THE PARSER: Place a breakpoint after this line and create a Watch with the following expression: Tree.PrintTree() //------- ERROR HANDLING ------- if (Tree.Errors.Any()) { // Used to disable error duplication var CheckDuplicate = new List <int>(); // Loop on every parsing error foreach (var err in Tree.Errors) { // Check if the error was already handled if (CheckDuplicate.Contains(err.Position) == false) { int col = 1; // Calculate the line and col in the file INCLUDING THE COMMENTS int line = CountLine(TSDefinitionOriginal, err.Position, out col); // Print a pretty message Logger.WriteError("Parsing error: invalid TypeScript Definition or unsupported feature", args[i] + ".d.ts", line, col); // Add this error position to handle it once CheckDuplicate.Add(err.Position); } } // Leave the compilation throw new Exception(); } //------- XML EXPORT ------- #if LOG_VERBOSE Logger.WriteMessage("Exporting file: " + OutputDirectory + args[i] + ".xml"); #endif // Create an XMLExporter XMLExporter Exporter = new XMLExporter(OutputDirectory + args[i] + ".xml"); // Store and remove useless nodes from the tree XMLExporter.ClearParseNode(Tree); // Export the tree as a XML file Exporter.PrintBasicTree(Tree); } else { #if LOG_VERBOSE Logger.WriteMessage(OutputDirectory + args[i] + ".xml is up to date!"); #endif DTSNew.TypeScriptDefinitionFiles.Add(file); //file.CSharpGeneratedFiles = new List<string>(); } } // Generate an intermediate version of the "NAME_OF_TYPESCRIPT_DEFINITIONS_CACHE_FILE" file so // that, if the export crashes, we don't re-parse again the TypeScript files // and, instead, we re-use the generated XML files: string newOutput = Tool.Serialize <TypeScriptDefToCSharpOutput>(DTSNew); File.WriteAllText(OutputDirectory + Constants.NAME_OF_TYPESCRIPT_DEFINITIONS_CACHE_FILE, newOutput); //-------------------------------------------------------- // PARSING XML AND GENERATE C# CODE //-------------------------------------------------------- //------- CHECK IF GENERATION IS NEEDED ------- // If only one generated file is missing we need to generate all of them // If we parsed a new .d.ts, we have new file to generate bool needToExport = false; for (int i = 0; i < args.Length && needToExport == false; i++) { var file = DTSNew.TypeScriptDefinitionFiles[i]; foreach (var f in file.CSharpGeneratedFiles) { if (File.Exists(f) == false) { needToExport = true; } } if (file.CSharpGeneratedFiles.Count == 0) // If there is no file, it's probably a new .d.ts { needToExport = true; } } //------- IF NEEDED GENERATE THE FILES ------- if (needToExport) { // Loop on every XML for (int i = 0; i < args.Length; i++) { var file = DTSNew.TypeScriptDefinitionFiles[i]; context.CurrentGeneratedFiles = file.CSharpGeneratedFiles = new List <string>(); #if LOG_VERBOSE Logger.WriteMessage("Parsing file: " + OutputDirectory + args[i] + ".xml"); #endif // Load the XML file var Doc = XDocument.Load(OutputDirectory + args[i] + ".xml"); // Create the model string fileName = Tool.RemoveDotIdentAdditionalChars(args[i].Replace('\\', '.')); // Note: we remove the unsupported characters because the TypeScript module name may contain some (such as forward slashes and dashes). var Prog = new GlobalProgram(Doc, fileName, context); // Add others files as "dependency" foreach (string Others in args) { if (Others != args[i]) { string n = Others.Replace('\\', '.'); n = Tool.RemoveDotIdentAdditionalChars(n); // Note: we remove the unsupported characters because the TypeScript Definition file name may contain some (such as dashes). Prog.Imports.Add(new Import() { Alias = n, Name = n }); } } // Export the model as C# Prog.Export(context); } // Write the Infos XML file before exporting AnonymousTypes, because these types // were added at the construction (allow associating them to a .d.ts file) newOutput = Tool.Serialize <TypeScriptDefToCSharpOutput>(DTSNew); File.WriteAllText(OutputDirectory + Constants.NAME_OF_TYPESCRIPT_DEFINITIONS_CACHE_FILE, newOutput); // Create the AnonymousTypes namespace Namespace Anon = new Namespace(null, context) { Name = "AnonymousTypes" }; // Add every namespace as a dependency for the AnonymousTypes namespace foreach (string Others in args) { var n = Tool.RemoveDotIdentAdditionalChars(Others); // Note: we remove the unsupported characters because the TypeScript Definition file name may contain some (such as dashes). Anon.Imports.Add(new Import() { Alias = n, Name = n }); } // Export every AnonymousType Directory.CreateDirectory(OutputDirectory + "AnonymousTypes"); foreach (AnonymousType a in AnonymousType.Anons) { a.Class.Super = Anon; a.Class.Export(context); } } else { Logger.WriteMessage(@"TypeScript Definition files were skipped because no change was detected. To force the reprocessing of TypeScript Definition files, manually delete the file ""obj\Debug\" + Constants.NAME_OF_TYPESCRIPT_DEFINITIONS_CACHE_FILE + @""" and rebuild."); } return(""); }