Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save nozzlegear/925a4995bc4b8e9d04e4c72ec298bf47 to your computer and use it in GitHub Desktop.
Save nozzlegear/925a4995bc4b8e9d04e4c72ec298bf47 to your computer and use it in GitHub Desktop.
This is a quick gist to document how I turned a few C# classes into TypeScript interfaces with NJSonSchema. I ended up replacing this program with Dogma (https://github.com/nozzlegear/dogma).
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using KMSignalR.Schemas;
using Microsoft.Extensions.CommandLineUtils;
using NJsonSchema;
using NJsonSchema.CodeGeneration;
using NJsonSchema.CodeGeneration.TypeScript;
using NJsonSchema.Generation;
namespace KMSignalR.Generator
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Generating TypeScript definitions.");
var app = new CommandLineApplication(false);
app.FullName = "KMSignalR Typings Generator";
app.Name = "KMSignalR.Generator";
app.Description = "Generates TypeScript typings from the KMSignalR.Schemas project.";
app.HelpOption("-h|--help|-?");
app.OnExecute(async () =>
{
var exports = new List<List<string>>();
Type[] types = {
typeof(KMSignalR.Schemas.AbstractSessionToken),
typeof(KMSignalR.Schemas.AuroraOrder),
typeof(KMSignalR.Schemas.ArtOrder),
typeof(KMSignalR.Schemas.PortraitOrder),
};
string[] autoGeneratedLines = null;
foreach (var type in types)
{
var schema = await JsonSchema4.FromTypeAsync(type, new JsonSchemaGeneratorSettings()
{
DefaultEnumHandling = EnumHandling.Integer
});
var tsGenerator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings()
{
TypeStyle = TypeScriptTypeStyle.Interface,
TypeScriptVersion = (decimal) 2.2,
TypeNameGenerator = new NameGenerator(),
});
string file = tsGenerator.GenerateFile();
while (file.Contains(Environment.NewLine + Environment.NewLine))
{
file = file.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine);
}
var lines = file.Split(Environment.NewLine.ToCharArray()).ToList();
int? startIndex = GetLineIndex(lines, "<auto-generated>");
int? endIndex = GetLineIndex(lines, "</auto-generated>");
if (! startIndex.HasValue || ! endIndex.HasValue || startIndex == endIndex)
{
exports.Add(lines);
continue;
}
if (autoGeneratedLines == null)
{
autoGeneratedLines = lines.Skip(startIndex.Value).Take(endIndex.Value - startIndex.Value + 1).ToArray();
}
int skip = endIndex.Value + 3; // Skip the </auto-generated>, newline, and //---- comment.
exports.Add(lines.Skip(skip).ToList());
}
File.WriteAllText("./typings/kmsignalr.generated.d.ts", ConcatToFile("kmsignalr/models", exports, autoGeneratedLines));
return 0;
});
app.Execute(args);
}
/// <summary>
/// Attempts to find the index of a line with the given string.
/// </summary>
private static int? GetLineIndex(List<string> lines, string searchFor)
{
var matches = lines.Where(l => l.Contains(searchFor));
if (matches.Count() == 0)
{
return null;
}
return lines.IndexOf(matches.First());
}
/// <summary>
/// Replaces empty lines in a list of strings.
/// </summary>
private static IEnumerable<string> ReplaceEmptyLines(IEnumerable<string> lines)
{
return lines.Where(line => ! string.IsNullOrEmpty(line));
}
/// <summary>
/// Finds duplicate 'export X' statements in a list of typescript lines, replacing the duplicates and leaving the original export.
/// </summary>
private static IEnumerable<string> FindDuplicateExports(IEnumerable<IEnumerable<string>> lines)
{
var allLines = lines.SelectMany(list => list).ToList();
var duplicates = allLines.Select((line, index) => new { Value = line, Index = index })
.Where(line => line.Value.Contains("export") && line.Value.Contains("{"))
.Where(line => allLines.Count(sourceLine => sourceLine == line.Value) > 1);
var ranges = new List<int>();
foreach (var export in duplicates.GroupBy(dupe => dupe.Value).Select(dupe => dupe.First()))
{
// Remove all duplicates except the first.
foreach (var duplicate in duplicates.Where(dupe => dupe.Value == export.Value).Skip(1))
{
int endIndex = allLines.IndexOf("}", duplicate.Index);
ranges.AddRange(Enumerable.Range(duplicate.Index, endIndex - duplicate.Index + 1));
}
}
return allLines.Where((_, index) => ! ranges.Contains(index)).ToList();
}
private static string ConcatToFile(string moduleName, IEnumerable<IEnumerable<string>> exports, IEnumerable<string> autoGeneratedWarning = null)
{
autoGeneratedWarning = autoGeneratedWarning ?? new string[] {};
string output = string.Join(Environment.NewLine, ReplaceEmptyLines(autoGeneratedWarning));
output += Environment.NewLine;
output += $"declare module \"{moduleName}\" {{";
output += Environment.NewLine;
output += string.Join("\n", ReplaceEmptyLines(FindDuplicateExports(exports)).Select(l => "\t" + l));
output += Environment.NewLine;
output += "}";
return output;
}
}
class NameGenerator : DefaultTypeNameGenerator
{
public override string Generate(JsonSchema4 schema, string typeNameHint, ICollection<string> reservedTypeNames)
{
string prefix = "Abstract";
if (typeNameHint.StartsWith(prefix))
{
return base.Generate(schema, typeNameHint.Substring(prefix.Length), reservedTypeNames);
}
return base.Generate(schema, typeNameHint, reservedTypeNames);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment