Example #1
0
        protected static (CSharpCompilation, NullCheckingEngine) CompileAndAnalyze(string program, CancellationToken cancellationToken = default)
        {
            var syntaxTree  = SyntaxFactory.ParseSyntaxTree(program, new CSharpParseOptions(LanguageVersion.CSharp8), cancellationToken: cancellationToken);
            var options     = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);
            var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, defaultReferences.Value, options);

            compilation = AllNullableSyntaxRewriter.MakeAllReferenceTypesNullable(compilation, cancellationToken);
            string allNullableText = compilation.SyntaxTrees.Single().GetText().ToString();

            foreach (var diag in compilation.GetDiagnostics(cancellationToken))
            {
                Assert.False(diag.Severity == DiagnosticSeverity.Error, diag.ToString() + "\r\n\r\nSource:\r\n" + program);
            }
            var engine = new NullCheckingEngine(compilation);

            engine.Analyze(ConflictResolutionStrategy.MinimizeWarnings, cancellationToken);
            return(compilation, engine);
        }
        /// <remarks>Used by reflection in CommandLineApplication.ExecuteAsync</remarks>
        private async Task <int> OnExecuteAsync(CommandLineApplication _)
        {
            var outputDirectory = new DirectoryInfo(Path.GetDirectoryName(ProjectName));

            if (await CouldOverwriteUncommittedFilesAsync(outputDirectory))
            {
                await Console.Error.WriteLineAsync($"WARNING: There are files in {outputDirectory.FullName} which may be overwritten, and aren't committed to git");

                if (Force)
                {
                    await Console.Error.WriteLineAsync("Continuing with possibility of data loss due to force option.");
                }
                else
                {
                    await Console.Error.WriteLineAsync("Aborting to avoid data loss (see above warning). Commit the files to git, or use the --force option to override this check.");

                    return(1);
                }
            }

            var buildProps = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["Configuration"] = "Debug",
                ["Platform"]      = "AnyCPU"
            };

            var cancellationToken = CancellationToken.None;

            using var workspace = await CreateWorkspaceAsync(buildProps);

            await Console.Error.WriteLineAsync("Loading project...");

            Project project;

            try {
                project = await workspace.OpenProjectAsync(ProjectName, cancellationToken : cancellationToken);
            } catch (Exception ex) {
                await Console.Error.WriteLineAsync(ex.ToString());

                return(1);
            }
            await Console.Error.WriteLineAsync("Compiling...");

            var compilation = await project.GetCompilationAsync(cancellationToken) as CSharpCompilation;

            if (compilation == null)
            {
                await Console.Error.WriteLineAsync("project.GetCompilationAsync() did not return CSharpCompilation");

                return(1);
            }
            compilation = AllNullableSyntaxRewriter.MakeAllReferenceTypesNullable(compilation, cancellationToken);
            bool hasErrors = false;

            foreach (var diag in compilation.GetDiagnostics(cancellationToken))
            {
                if (diag.Severity == DiagnosticSeverity.Error)
                {
                    await Console.Error.WriteLineAsync(diag.ToString());

                    hasErrors = true;
                }
            }
            if (hasErrors)
            {
                await Console.Error.WriteLineAsync("Compilation failed. Cannot infer nullability.");

                return(1);
            }
            await Console.Error.WriteLineAsync("Inferring nullabilities...");

            var engine = new NullCheckingEngine(compilation);

            engine.Analyze(cancellationToken);
#if DEBUG
            if (ShowGraph)
            {
                await Console.Error.WriteLineAsync("Showing graph...");

                engine.ExportTypeGraph().Show();
            }
#endif
            if (DryRun)
            {
                await Console.Error.WriteLineAsync("Analysis successful. Results are discarded due to --dry-run.");
            }
            else
            {
                await Console.Error.WriteLineAsync("Writing modified code...");

                engine.ConvertSyntaxTrees(cancellationToken).ForAll(tree => {
                    if (string.IsNullOrEmpty(tree.FilePath))
                    {
                        return;
                    }
                    using var stream = new FileStream(tree.FilePath, FileMode.Create, FileAccess.Write);
                    using var writer = new StreamWriter(stream, tree.Encoding);
                    writer.Write(tree.GetText(cancellationToken));
                });
            }
            await Console.Error.WriteLineAsync("Success!");

            return(0);
        }
        /// <remarks>Used by reflection in CommandLineApplication.ExecuteAsync</remarks>
        private async Task <int> OnExecuteAsync(CommandLineApplication _)
        {
            var outputDirectory = new DirectoryInfo(Path.GetDirectoryName(ProjectName));

            if (await CouldOverwriteUncommittedFilesAsync(outputDirectory))
            {
                await Console.Error.WriteLineAsync($"WARNING: There are files in {outputDirectory.FullName} which may be overwritten, and aren't committed to git");

                if (Force)
                {
                    await Console.Error.WriteLineAsync("Continuing with possibility of data loss due to force option.");
                }
                else
                {
                    await Console.Error.WriteLineAsync("Aborting to avoid data loss (see above warning). Commit the files to git, or use the --force option to override this check.");

                    return(1);
                }
            }

            var buildProps = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["Configuration"] = "Debug",
                ["Platform"]      = "AnyCPU"
            };

            var cancellationToken = CancellationToken.None;

            using var workspace = await CreateWorkspaceAsync(buildProps);

            await Console.Error.WriteLineAsync("Loading project...");

            Project project;

            try {
                project = await workspace.OpenProjectAsync(ProjectName, cancellationToken : cancellationToken);
            } catch (Exception ex) {
                await Console.Error.WriteLineAsync(ex.ToString());

                return(1);
            }
            await Console.Error.WriteLineAsync("Compiling...");

            var compilation = await project.GetCompilationAsync(cancellationToken) as CSharpCompilation;

            if (compilation == null)
            {
                await Console.Error.WriteLineAsync("project.GetCompilationAsync() did not return CSharpCompilation");

                return(1);
            }
            compilation = AllNullableSyntaxRewriter.MakeAllReferenceTypesNullable(compilation, cancellationToken);
            if (AllNullable)
            {
                await Console.Error.WriteLineAsync("Writing modified code...");

                foreach (var tree in compilation.SyntaxTrees)
                {
                    WriteTree(tree, cancellationToken);
                }
                return(0);
            }
            bool hasErrors = false;

            foreach (var diag in compilation.GetDiagnostics(cancellationToken))
            {
                if (diag.Severity == DiagnosticSeverity.Error)
                {
                    await Console.Error.WriteLineAsync(diag.ToString());

                    hasErrors = true;
                }
            }
            if (hasErrors)
            {
                await Console.Error.WriteLineAsync("Compilation failed. Cannot infer nullability.");

                return(1);
            }
            await Console.Error.WriteLineAsync("Inferring nullabilities...");

            var engine = new NullCheckingEngine(compilation);

            engine.Analyze(this.Strategy, cancellationToken);
#if DEBUG
            GraphVizGraph?exportedGraph = null;
            if (ShowGraph)
            {
                await Console.Error.WriteLineAsync("Showing graph...");

                exportedGraph ??= ExportTypeGraph(engine);
                exportedGraph.Show();
            }
            if (ExportGraph != null)
            {
                await Console.Error.WriteLineAsync("Exporting graph...");

                exportedGraph ??= ExportTypeGraph(engine);
                exportedGraph.Save(ExportGraph);
            }
#endif
            Statistics stats;
            if (DryRun)
            {
                await Console.Error.WriteLineAsync("Computing statistics...");

                stats = engine.ConvertSyntaxTrees(cancellationToken, tree => { });
                await Console.Error.WriteLineAsync("Analysis successful. Results are discarded due to --dry-run.");

                await Console.Error.WriteLineAsync("Would use:");
            }
            else
            {
                await Console.Error.WriteLineAsync("Writing modified code...");

                stats = engine.ConvertSyntaxTrees(cancellationToken, tree => WriteTree(tree, cancellationToken));
                await Console.Error.WriteLineAsync("Success!");

                await Console.Error.WriteLineAsync("Used:");
            }
            await Console.Error.WriteLineAsync($"  {stats.NullableCount} nullable reference types.");

            await Console.Error.WriteLineAsync($"  {stats.NonNullCount} non-nullable reference types.");

            await Console.Error.WriteLineAsync($"  {stats.NotNullWhenCount} [NotNullWhen] attributes.");

            return(0);
        }