public async Task TestLibraryRoot() { var projectPath = Path.Combine(Environment.CurrentDirectory, "..", "..", "..", "..", "TestProjects", "TestLibraryRoot.sln"); var clock = Stopwatch.StartNew(); var result = CSharpCompilationCapture.Build(projectPath); Console.WriteLine($"Project {projectPath} built in {clock.Elapsed.TotalMilliseconds}ms"); var workspace = result.Workspace; // TestLibrary1 // TestLibrary2 // TestLibraryRoot (netcoreapp3.1) + TestLibraryRoot (netstandard2.0) Assert.AreEqual(1 + 1 + 2, workspace.CurrentSolution.Projects.Count()); Assert.True(workspace.CurrentSolution.Projects.Any(p => p.Name == "TestLibrary1"), "Expecting TestLibrary1 in the projects"); Assert.True(workspace.CurrentSolution.Projects.Any(p => p.Name == "TestLibrary2"), "Expecting TestLibrary1 in the projects"); Assert.AreEqual(2, workspace.CurrentSolution.Projects.Count(p => p.Name == "TestLibraryRoot"), "Expecting 2 TestLibraryRoot projects"); foreach (var project in workspace.CurrentSolution.Projects) { clock.Restart(); Assert.AreEqual(OptimizationLevel.Debug, project.CompilationOptions.OptimizationLevel); var hasArguments = result.TryGetCommandLineArguments(project, out var arguments); Assert.True(hasArguments, "Unable to retrieve CSharp Arguments by Project"); hasArguments = result.TryGetCommandLineArguments(project.Id, out arguments); Assert.True(hasArguments, "Unable to retrieve CSharp Arguments by ProjectId"); // Compile the project var compilation = await project.GetCompilationAsync(); Assert.NotNull(compilation, "Compilation must not be null"); var diagnostics = compilation.GetDiagnostics(); var errors = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error).ToList(); if (errors.Count > 0) { var builder = new StringBuilder(); builder.AppendLine($"Project {project.Name} has errors:"); foreach (var diag in errors) { builder.AppendLine($"{diag}"); } Assert.AreEqual(0, errors.Count, $"Invalid compilation errors: {builder}"); } else { Console.WriteLine($"Project {project.Name} ({project.CompilationOptions.OptimizationLevel}) in-memory compiled in {clock.Elapsed.TotalMilliseconds}ms"); // We should have at least 1 syntax tree var trees = compilation.SyntaxTrees.ToList(); Assert.AreNotEqual(0, trees.Count, "Expecting SyntaxTrees"); } } }
static async Task Main(string[] args) { var x = typeof(System.Composition.CompositionContext).Name; var rootFolder = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"../../../../..")); var siteFolder = Path.Combine(rootFolder, "site"); var srcFolder = Path.Combine(rootFolder, "src"); var testsFolder = Path.Combine(srcFolder, "Kalk.Tests"); var pathToSolution = Path.Combine(srcFolder, @"Kalk.Core", "Kalk.Core.csproj"); var broResult = CSharpCompilationCapture.Build(pathToSolution); var solution = broResult.Workspace.CurrentSolution; var project = solution.Projects.First(x => x.Name == "Kalk.Core"); // Make sure that doc will be parsed project = project.WithParseOptions(project.ParseOptions.WithDocumentationMode(DocumentationMode.Parse)); // Compile the project var compilation = await project.GetCompilationAsync(); var errors = compilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ToList(); if (errors.Count > 0) { Console.WriteLine("Compilation errors:"); foreach (var error in errors) { Console.WriteLine(error); } Console.WriteLine("Error, Exiting."); Environment.Exit(1); return; } //var kalkEngine = compilation.GetTypeByMetadataName("Kalk.Core.KalkEngine"); var mapNameToModule = new Dictionary <string, KalkModuleToGenerate>(); void GetOrCreateModule(ITypeSymbol typeSymbol, string className, AttributeData moduleAttribute, out KalkModuleToGenerate moduleToGenerate) { if (!mapNameToModule.TryGetValue(className, out moduleToGenerate)) { moduleToGenerate = new KalkModuleToGenerate() { Namespace = typeSymbol.ContainingNamespace.ToDisplayString(), ClassName = className, }; mapNameToModule.Add(className, moduleToGenerate); if (moduleAttribute != null) { moduleToGenerate.Name = moduleAttribute.ConstructorArguments[0].Value.ToString(); moduleToGenerate.Names.Add(moduleToGenerate.Name); moduleToGenerate.Category = "Modules (e.g `import Files`)"; } else { moduleToGenerate.Name = className.Replace("Module", ""); moduleToGenerate.IsBuiltin = true; } ExtractDocumentation(typeSymbol, moduleToGenerate); } } foreach (var type in compilation.GetSymbolsWithName(x => true, SymbolFilter.Type)) { var typeSymbol = type as ITypeSymbol; if (typeSymbol == null) { continue; } var moduleAttribute = typeSymbol.GetAttributes().FirstOrDefault(x => x.AttributeClass.Name == "KalkExportModuleAttribute"); KalkModuleToGenerate moduleToGenerate = null; if (moduleAttribute != null) { GetOrCreateModule(typeSymbol, typeSymbol.Name, moduleAttribute, out moduleToGenerate); } foreach (var member in typeSymbol.GetMembers()) { var attr = member.GetAttributes().FirstOrDefault(x => x.AttributeClass.Name == "KalkExportAttribute"); if (attr == null) { continue; } var name = attr.ConstructorArguments[0].Value.ToString(); var category = attr.ConstructorArguments[1].Value.ToString(); var containingType = member.ContainingSymbol; var className = containingType.Name; // In case the module is built-in, we still generate a module for it if (moduleToGenerate == null) { GetOrCreateModule(typeSymbol, className, moduleAttribute, out moduleToGenerate); } var method = member as IMethodSymbol; var desc = new KalkMemberToGenerate() { Name = name, XmlId = member.GetDocumentationCommentId(), Category = category, IsCommand = method != null && method.ReturnsVoid, Module = moduleToGenerate, }; desc.Names.Add(name); if (method != null) { desc.CSharpName = method.Name; var builder = new StringBuilder(); desc.IsAction = method.ReturnsVoid; desc.IsFunc = !desc.IsAction; builder.Append(desc.IsAction ? "Action" : "Func"); if (method.Parameters.Length > 0 || desc.IsFunc) { builder.Append("<"); } for (var i = 0; i < method.Parameters.Length; i++) { var parameter = method.Parameters[i]; if (i > 0) { builder.Append(", "); } builder.Append(GetTypeName(parameter.Type)); } if (desc.IsFunc) { if (method.Parameters.Length > 0) { builder.Append(", "); } builder.Append(GetTypeName(method.ReturnType)); } if (method.Parameters.Length > 0 || desc.IsFunc) { builder.Append(">"); } desc.Cast = $"({builder.ToString()})"; } else if (member is IPropertySymbol || member is IFieldSymbol) { desc.CSharpName = member.Name; desc.IsConst = true; } moduleToGenerate.Members.Add(desc); ExtractDocumentation(member, desc); } } var modules = mapNameToModule.Values.OrderBy(x => x.ClassName).ToList(); var templateStr = @"//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Date: {{ date.now }} // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ using System; {{~ func GenerateDocDescriptor(item) ~}} { var descriptor = {{ if item.IsModule }}Descriptor{{ else }}Descriptors[""{{ item.Name }}""]{{ end }}; descriptor.Category = ""{{ item.Category }}""; descriptor.Description = @""{{ item.Description | string.replace '""' '""""' }}""; descriptor.IsCommand = {{ item.IsCommand }}; {{~ for param in item.Params ~}} descriptor.Params.Add(new KalkParamDescriptor(""{{ param.Name }}"", @""{{ param.Description | string.replace '""' '""""' }}"") { IsOptional = {{ param.IsOptional }} }); {{~ end ~}} {{~ if item.Returns ~}} descriptor.Returns = @""{{ item.Returns | string.replace '""' '""""' }}""; {{~ end ~}} {{~ if item.Remarks ~}} descriptor.Remarks = @""{{ item.Remarks | string.replace '""' '""""' }}""; {{~ end ~}} {{~ if item.Example ~}} descriptor.Example = @""{{ item.Example | string.replace '""' '""""' }}""; {{~ end ~}} } {{~ end ~}} {{~ for module in modules ~}} namespace {{ module.Namespace }} { public partial class {{ module.ClassName }} { {{~ if module.Name != 'All' ~}} {{~ if module.ClassName == 'KalkEngine' ~}} protected void RegisterFunctionsAuto() {{~ else ~}} protected override void RegisterFunctionsAuto() {{~ end ~}} { {{~ for member in module.Members ~}} {{~ if member.IsConst ~}} RegisterConstant(""{{ member.Name }}"", {{ member.CSharpName }}); {{~ else if member.IsFunc ~}} RegisterFunction(""{{ member.Name }}"", {{member.Cast}}{{ member.CSharpName }}); {{~ else if member.IsAction ~}} RegisterAction(""{{ member.Name }}"", {{member.Cast}}{{ member.CSharpName }}); {{~ end ~}} {{~ end ~}} RegisterDocumentationAuto(); } {{~ end ~}} private void RegisterDocumentationAuto() { {{~ if !module.IsBuiltin GenerateDocDescriptor module end for item in module.Members GenerateDocDescriptor item end ~}} } } } {{~ end ~}} "; var template = Template.Parse(templateStr); var result = template.Render(new { modules = modules }, x => x.Name); File.WriteAllText(Path.Combine(srcFolder, "Kalk.Core/KalkEngine.generated.cs"), result); var testsTemplateStr = @"//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Date: {{ date.now }} // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ using System; using NUnit.Framework; namespace Kalk.Tests { {{~ imported = {}; for module in modules; if !(imported[module.Namespace]); imported[module.Namespace] = true; ~}} using {{ module.Namespace }}; {{~ end; end ~}} {{~ for module in modules ~}} public partial class {{ module.ClassName }}Tests : KalkTestBase { {{~ for member in module.Members ~}} {{~ if (array.size member.Tests) > 0 ~}} {{~ for test in member.Tests ~}} /// <summary> /// Test for <see cref=""{{ member.XmlId }}""/> or `{{ member.Name }}`. /// </summary> [TestCase(@""{{ test.Item1 | string.replace '""' '""""' }}"", @""{{ test.Item2 | string.replace '""' '""""' }}"", Category = ""{{ member.Category }}"")] {{~ end ~}} {{~ if module.IsBuiltin ~}} public static void Test_{{ member.Name }}(string input, string output) => AssertScript(input, output); {{~ else ~}} public static void Test_{{ member.Name }}(string input, string output) => AssertScript(input, output, ""{{module.Name}}""); {{~ end ~}} {{~ end ~}} {{~ end ~}} } {{~ end ~}} } "; var templateTests = Template.Parse(testsTemplateStr); result = templateTests.Render(new { modules = modules }, x => x.Name); File.WriteAllText(Path.Combine(testsFolder, "KalkTests.generated.cs"), result); // Prism templates var prismTemplateStr = @"Prism.languages.kalk.function = /\b(?:{{ functions | array.join '|' }})\b/; "; var prismTemplate = Template.Parse(prismTemplateStr); var allFunctionNames = modules.SelectMany(x => x.Members.Select(y => y.Name)).ToList(); // special values allFunctionNames.AddRange(new [] { "null", "true", "false" }); // modules allFunctionNames.AddRange(new[] { "All", "Csv", "Currencies", "Files", "HardwareIntrinsics", "StandardUnits", "Strings", "Web" }); allFunctionNames = allFunctionNames.OrderByDescending(x => x.Length).ThenBy(x => x).ToList(); result = prismTemplate.Render(new { functions = allFunctionNames }); File.WriteAllText(Path.Combine(siteFolder, ".lunet", "js", "prism-kalk.generated.js"), result); // Generate module site documentation foreach (var module in modules) { GenerateModuleSiteDocumentation(module, siteFolder); } // Log any errors if a member doesn't have any doc or tests int functionWithMissingDoc = 0; int functionWithMissingTests = 0; foreach (var module in modules) { foreach (var member in module.Members) { var hasNoDesc = string.IsNullOrEmpty(member.Description); var hasNoTests = member.Tests.Count == 0; if ((hasNoDesc || hasNoTests) && !module.ClassName.Contains("Intrinsics")) { // We don't log for all the matrix constructors, as they are tested separately. if (module.ClassName != "TypesModule" || !member.CSharpName.StartsWith("Create")) { if (hasNoDesc) { ++functionWithMissingDoc; } if (hasNoTests) { ++functionWithMissingTests; } Console.WriteLine($"The member {member.Name} => {module.ClassName}.{member.CSharpName} doesn't have {(hasNoTests ? "any tests" + (hasNoDesc ? " and" : "") : "")} {(hasNoDesc ? "any docs" : "")}"); } } } } Console.WriteLine($"{modules.Count} modules generated."); Console.WriteLine($"{modules.SelectMany(x => x.Members).Count()} functions generated."); Console.WriteLine($"{modules.SelectMany(x => x.Members).SelectMany(y => y.Tests).Count()} tests generated."); Console.WriteLine($"{functionWithMissingDoc} functions with missing doc."); Console.WriteLine($"{functionWithMissingTests} functions with missing tests."); }