Esempio n. 1
0
        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");
                }
            }
        }
Esempio n. 2
0
        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.");
        }