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); }
private GraphVizGraph ExportTypeGraph(NullCheckingEngine engine) { if (FilterGraph.Count == 0) { // Show complete graph return(engine.ExportTypeGraph()); } else { // Show filtered graph var list = new List <(string file, int start, int end)>(); foreach (var entry in FilterGraph) { var m = Regex.Match(entry, @"^([\w_.-]+):(\d+)-(\d+)$"); if (!m.Success) { Console.WriteLine("Invalid value for --show-graph/--export-graph. Expected filename.cs:100-200"); } list.Add((m.Groups[1].Value, int.Parse(m.Groups[2].Value), int.Parse(m.Groups[3].Value))); } return(engine.ExportTypeGraph(location => list.Any(e => MatchesEntry(e, location))));
/// <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); }