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();
}
}
}