using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using Semmle.Util; using Semmle.Util.Logging; namespace Semmle.Extraction.CSharp { public interface IOverlayInfo { /// /// True, if the extractor is running in overlay mode. /// bool IsOverlayMode { get; } /// /// Returns true, if the given file is not in the set of changed files. /// /// A source file path bool OnlyMakeScaffold(string filePath); } /// /// An instance of this class is used when overlay is not enabled. /// public class TrivialOverlayInfo : IOverlayInfo { public TrivialOverlayInfo() { } public bool IsOverlayMode { get; } = false; public bool OnlyMakeScaffold(string filePath) => false; } /// /// An instance of this class is used for detecting /// (1) Whether overlay is enabled. /// (2) Fetch the changed files that should be fully extracted as a part /// of the overlay extraction. /// public class OverlayInfo : IOverlayInfo { private readonly ILogger logger; private readonly HashSet changedFiles; private readonly string srcDir; public OverlayInfo(ILogger logger, string srcDir, string json) { this.logger = logger; this.srcDir = srcDir; changedFiles = ParseJson(json); } public bool IsOverlayMode { get; } = true; public bool OnlyMakeScaffold(string filePath) => !changedFiles.Contains(filePath); /// /// Private type only used to parse overlay changes JSON files. /// /// The content of such a file has the format /// { /// "changes": [ /// "app/controllers/about_controller.xyz", /// "app/models/about.xyz" /// ] /// } /// private record ChangedFiles { public string[]? Changes { get; set; } } private HashSet ParseJson(string json) { try { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var obj = JsonSerializer.Deserialize(json, options); return obj?.Changes is string[] changes ? changes.Select(change => Path.Join(srcDir, change)).ToHashSet() : []; } catch (JsonException) { logger.LogError("Overlay: Unable to parse the JSON content from the overlay changes file."); return []; } } } public static class OverlayInfoFactory { /// /// The returned object is used to decide, whether /// (1) The extractor is running in overlay mode. /// (2) Which files to only extract scaffolds for (unchanged files) /// /// A logger /// The (overlay) source directory /// An overlay information object. public static IOverlayInfo Make(ILogger logger, string srcDir) { if (EnvironmentVariables.GetOverlayChangesFilePath() is string path) { logger.LogInfo($"Overlay: Reading overlay changes from file '{path}'."); try { var json = File.ReadAllText(path); return new OverlayInfo(logger, srcDir, json); } catch { logger.LogError("Overlay: Unexpected error while reading the overlay changes file."); } } logger.LogInfo("Overlay: Overlay mode not enabled."); return new TrivialOverlayInfo(); } } }