docs: complete XML doc coverage (returns, summaries, inheritdoc)

Resolve all 622 issues flagged by the enhanced CommentChecker: add missing
<returns> tags (incl. the standard phrasing on non-generic Task methods),
add missing <summary> tags, and replace misused/redundant <inheritdoc/> on
members that override or implement nothing with real documentation.
Documentation-only — no behavior change; solution builds clean.
This commit is contained in:
Joseph Doherty
2026-06-03 11:39:32 -04:00
parent a050170414
commit eabf270d71
208 changed files with 867 additions and 114 deletions
@@ -17,6 +17,7 @@ public static class CycleDetector
/// A plain <c>ToDictionary(t =&gt; t.Id)</c> would instead throw ArgumentException.
/// </summary>
/// <param name="allTemplates">All templates to build lookup from.</param>
/// <returns>A dictionary keyed by template Id; on duplicate Ids the first occurrence wins.</returns>
internal static Dictionary<int, Template> BuildLookup(IReadOnlyList<Template> allTemplates)
{
var lookup = new Dictionary<int, Template>();
@@ -463,6 +463,7 @@ public class FlatteningService
/// </summary>
/// <param name="inheritedJson">The parent template's HiLo trigger JSON, or null.</param>
/// <param name="derivedJson">The child template's HiLo trigger JSON override, or null.</param>
/// <returns>The merged HiLo config JSON, or the derived config on parse failure of either input.</returns>
public static string? MergeHiLoConfig(string? inheritedJson, string? derivedJson)
{
if (string.IsNullOrWhiteSpace(inheritedJson)) return derivedJson;
@@ -526,6 +527,7 @@ public class FlatteningService
/// </summary>
/// <param name="inheritedJson">The parent template's HiLo trigger JSON, or null.</param>
/// <param name="editedJson">The user-edited HiLo trigger JSON, or null.</param>
/// <returns>A JSON string containing only the keys that differ from the inherited config, or null if there are no differences.</returns>
public static string? DiffHiLoConfig(string? inheritedJson, string? editedJson)
{
if (string.IsNullOrWhiteSpace(editedJson)) return null;
@@ -34,6 +34,7 @@ public class RevisionHashService
/// excluding volatile fields like GeneratedAtUtc.
/// </summary>
/// <param name="configuration">The flattened configuration to hash.</param>
/// <returns>A hex-encoded SHA-256 hash string of the canonical configuration representation.</returns>
public string ComputeHash(FlattenedConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(configuration);
@@ -27,6 +27,7 @@ public static class LockEnforcer
/// </summary>
/// <param name="original">The parent template's attribute definition.</param>
/// <param name="proposed">The child template's proposed override.</param>
/// <returns>An error message string if a rule is violated; <c>null</c> if the override is permitted.</returns>
public static string? ValidateAttributeOverride(
TemplateAttribute original,
TemplateAttribute proposed)
@@ -56,6 +57,7 @@ public static class LockEnforcer
/// </summary>
/// <param name="original">The parent template's alarm definition.</param>
/// <param name="proposed">The child template's proposed override.</param>
/// <returns>An error message string if a rule is violated; <c>null</c> if the override is permitted.</returns>
public static string? ValidateAlarmOverride(
TemplateAlarm original,
TemplateAlarm proposed)
@@ -85,6 +87,7 @@ public static class LockEnforcer
/// </summary>
/// <param name="original">The parent template's script definition.</param>
/// <param name="proposed">The child template's proposed override.</param>
/// <returns>An error message string if a rule is violated; <c>null</c> if the override is permitted.</returns>
public static string? ValidateScriptOverride(
TemplateScript original,
TemplateScript proposed)
@@ -110,6 +113,7 @@ public static class LockEnforcer
/// <param name="originalIsLocked">The current lock state of the member.</param>
/// <param name="proposedIsLocked">The proposed lock state.</param>
/// <param name="memberName">Name of the member being changed, for error messages.</param>
/// <returns>An error message if unlocking is attempted; <c>null</c> if the lock change is valid.</returns>
public static string? ValidateLockChange(bool originalIsLocked, bool proposedIsLocked, string memberName)
{
if (originalIsLocked && !proposedIsLocked)
@@ -130,6 +134,7 @@ public static class LockEnforcer
/// <param name="originalLockedInDerived">Current <c>LockedInDerived</c> state.</param>
/// <param name="proposedLockedInDerived">Proposed <c>LockedInDerived</c> state.</param>
/// <param name="memberName">Name of the member being changed, for error messages.</param>
/// <returns>An error message if the locked-in-derived flag is being cleared; <c>null</c> if the change is valid.</returns>
public static string? ValidateLockedInDerivedChange(
bool originalLockedInDerived,
bool proposedLockedInDerived,
@@ -11,6 +11,7 @@ public static class ServiceCollectionExtensions
/// Registers all template engine services (template, flattening, validation, and domain services).
/// </summary>
/// <param name="services">The service collection to register into.</param>
/// <returns>The same <paramref name="services"/> instance for chaining.</returns>
public static IServiceCollection AddTemplateEngine(this IServiceCollection services)
{
services.AddScoped<TemplateService>();
@@ -43,6 +44,7 @@ public static class ServiceCollectionExtensions
/// Registers Akka.NET actors for the template engine (placeholder for future actor registration).
/// </summary>
/// <param name="services">The service collection to register into.</param>
/// <returns>The same <paramref name="services"/> instance for chaining.</returns>
public static IServiceCollection AddTemplateEngineActors(this IServiceCollection services)
{
// Phase 0: placeholder for Akka actor registration
@@ -35,6 +35,7 @@ public class AreaService
/// <param name="parentAreaId">Optional parent area identifier for hierarchical organization.</param>
/// <param name="user">The user performing the action for audit logging.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a success result containing the new area, or a failure result with an error message.</returns>
public async Task<Result<Area>> CreateAreaAsync(
string name, int siteId, int? parentAreaId, string user,
CancellationToken cancellationToken = default)
@@ -81,6 +82,7 @@ public class AreaService
/// <param name="name">The new name for the area.</param>
/// <param name="user">The user performing the action for audit logging.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a success result containing the updated area, or a failure result with an error message.</returns>
public async Task<Result<Area>> UpdateAreaAsync(
int areaId, string name, string user,
CancellationToken cancellationToken = default)
@@ -118,6 +120,7 @@ public class AreaService
/// <param name="newParentAreaId">The new parent area identifier, or null to move to site root.</param>
/// <param name="user">The user performing the action for audit logging.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a success result containing the moved area, or a failure result with an error message.</returns>
public async Task<Result<Area>> MoveAreaAsync(
int areaId, int? newParentAreaId, string user,
CancellationToken cancellationToken = default)
@@ -175,6 +178,7 @@ public class AreaService
/// <param name="areaId">The area identifier.</param>
/// <param name="user">The user performing the action for audit logging.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a success result with <c>true</c>, or a failure result if deletion is blocked.</returns>
public async Task<Result<bool>> DeleteAreaAsync(
int areaId, string user,
CancellationToken cancellationToken = default)
@@ -224,6 +228,7 @@ public class AreaService
/// </summary>
/// <param name="siteId">The site identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a read-only list of all areas for the site.</returns>
public async Task<IReadOnlyList<Area>> GetAreasBySiteIdAsync(int siteId, CancellationToken cancellationToken = default) =>
await _repository.GetAreasBySiteIdAsync(siteId, cancellationToken);
@@ -232,6 +237,7 @@ public class AreaService
/// </summary>
/// <param name="areaId">The area identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the matching area, or null if not found.</returns>
public async Task<Area?> GetAreaByIdAsync(int areaId, CancellationToken cancellationToken = default) =>
await _repository.GetAreaByIdAsync(areaId, cancellationToken);
@@ -112,11 +112,13 @@ public class SiteService
/// <summary>Returns the site with the given primary key, or <c>null</c> if not found.</summary>
/// <param name="siteId">Primary key of the site.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the matching <see cref="Site"/>, or <c>null</c> if not found.</returns>
public async Task<Site?> GetSiteByIdAsync(int siteId, CancellationToken cancellationToken = default) =>
await _repository.GetSiteByIdAsync(siteId, cancellationToken);
/// <summary>Returns all sites.</summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the complete list of all sites.</returns>
public async Task<IReadOnlyList<Site>> GetAllSitesAsync(CancellationToken cancellationToken = default) =>
await _repository.GetAllSitesAsync(cancellationToken);
@@ -30,6 +30,7 @@ public class TemplateDeletionService
/// </summary>
/// <param name="templateId">The id of the template to check.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task that resolves to a successful result if the template can be deleted, or a failure with blocking reasons.</returns>
public async Task<Result<bool>> CanDeleteTemplateAsync(int templateId, CancellationToken cancellationToken = default)
{
var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
@@ -106,6 +107,7 @@ public class TemplateDeletionService
/// </summary>
/// <param name="templateId">The id of the template to delete.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task that resolves to a successful result when deleted, or a failure with the blocking reason.</returns>
public async Task<Result<bool>> DeleteTemplateAsync(int templateId, CancellationToken cancellationToken = default)
{
var canDelete = await CanDeleteTemplateAsync(templateId, cancellationToken);
@@ -28,6 +28,7 @@ public class TemplateFolderService
/// <param name="parentFolderId">Parent folder id, or null to create at root level.</param>
/// <param name="user">Username of the actor performing the operation (for audit).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the created <see cref="TemplateFolder"/>, or a failure result if validation fails.</returns>
public async Task<Result<TemplateFolder>> CreateFolderAsync(
string name, int? parentFolderId, string user,
CancellationToken cancellationToken = default)
@@ -62,6 +63,7 @@ public class TemplateFolderService
/// <param name="newName">New display name for the folder.</param>
/// <param name="user">Username of the actor performing the operation (for audit).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the updated <see cref="TemplateFolder"/>, or a failure result if the folder is not found or the name collides.</returns>
public async Task<Result<TemplateFolder>> RenameFolderAsync(
int folderId, string newName, string user,
CancellationToken cancellationToken = default)
@@ -94,6 +96,7 @@ public class TemplateFolderService
/// <param name="newParentId">Target parent folder id, or null to move to root level.</param>
/// <param name="user">Username of the actor performing the operation (for audit).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the moved <see cref="TemplateFolder"/>, or a failure result if a cycle or name collision is detected.</returns>
public async Task<Result<TemplateFolder>> MoveFolderAsync(
int folderId, int? newParentId, string user,
CancellationToken cancellationToken = default)
@@ -155,6 +158,7 @@ public class TemplateFolderService
/// <param name="folderId">Id of the folder to delete.</param>
/// <param name="user">Username of the actor performing the operation (for audit).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a success result if the folder was deleted, or a failure result if the folder is not found or not empty.</returns>
public async Task<Result<bool>> DeleteFolderAsync(
int folderId, string user,
CancellationToken cancellationToken = default)
@@ -59,6 +59,7 @@ internal static class CSharpDelimiterScanner
/// </summary>
/// <param name="code">The C# source code to scan.</param>
/// <param name="pattern">The substring to search for in code regions only.</param>
/// <returns><c>true</c> if <paramref name="pattern"/> occurs in a code region (not inside a comment, string, or char literal); otherwise <c>false</c>.</returns>
internal static bool ContainsInCode(string code, string pattern)
{
if (string.IsNullOrEmpty(pattern))
@@ -162,6 +163,7 @@ internal static class CSharpDelimiterScanner
/// ignored.
/// </summary>
/// <param name="code">The C# source code to scan for delimiter balance.</param>
/// <returns>The first <see cref="Mismatch"/> found, or <see cref="Mismatch.None"/> if all delimiters are balanced.</returns>
internal static Mismatch Scan(string code)
{
int brace = 0, bracket = 0, paren = 0;
@@ -25,6 +25,8 @@ public class SemanticValidator
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <param name="sharedScripts">Shared scripts available for CallShared references.</param>
/// <param name="alarmCapableConnectionNames">Connection names that support alarm subscriptions; used to validate native alarm source bindings.</param>
/// <returns>A <see cref="ValidationResult"/> containing all semantic errors and warnings found.</returns>
public ValidationResult Validate(
FlattenedConfiguration configuration,
IReadOnlyList<ResolvedScript>? sharedScripts = null,
@@ -288,6 +290,7 @@ public class SemanticValidator
/// Parses a parameter definitions JSON string (JSON Schema or legacy flat array) and returns the declared parameter names.
/// </summary>
/// <param name="parameterDefinitionsJson">JSON Schema or legacy flat-array string; null/empty returns an empty list.</param>
/// <returns>The list of parameter names declared in the definition.</returns>
internal static List<string> ParseParameterDefinitions(string? parameterDefinitionsJson)
{
if (string.IsNullOrWhiteSpace(parameterDefinitionsJson))
@@ -325,6 +328,7 @@ public class SemanticValidator
/// Looks for CallScript("name", ...) and CallShared("name", ...) patterns.
/// </summary>
/// <param name="code">The script source code to scan.</param>
/// <returns>The list of call targets found (both <c>CallScript</c> and <c>CallShared</c> invocations).</returns>
internal static List<CallTarget> ExtractCallTargets(string code)
{
var results = new List<CallTarget>();
@@ -45,6 +45,7 @@ public class ValidationService
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <param name="sharedScripts">Optional list of shared scripts for validation context.</param>
/// <returns>A merged <see cref="ValidationResult"/> aggregating all pipeline stage outcomes.</returns>
public ValidationResult Validate(FlattenedConfiguration configuration, IReadOnlyList<ResolvedScript>? sharedScripts = null)
{
ArgumentNullException.ThrowIfNull(configuration);
@@ -68,6 +69,7 @@ public class ValidationService
/// Validates that flattening produced a non-empty configuration.
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with errors or warnings if the configuration is empty or missing a name; otherwise success.</returns>
public static ValidationResult ValidateFlatteningSuccess(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -97,6 +99,7 @@ public class ValidationService
/// Canonical names must be unique within their entity type (attributes, alarms, scripts).
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with errors for each duplicate canonical name, or success.</returns>
public static ValidationResult ValidateNamingCollisions(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -114,6 +117,7 @@ public class ValidationService
/// Validates that all scripts compile successfully using the ScriptCompiler.
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with errors for each script that fails compilation.</returns>
public ValidationResult ValidateScriptCompilation(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -138,6 +142,7 @@ public class ValidationService
/// Alarm trigger configs are JSON with an "attributeName" field referencing a canonical attribute name.
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with errors for any alarm whose trigger references a missing attribute.</returns>
public static ValidationResult ValidateAlarmTriggerReferences(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -167,6 +172,7 @@ public class ValidationService
/// Validates that script trigger configurations reference existing attributes.
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with errors for any script whose trigger references a missing attribute.</returns>
public static ValidationResult ValidateScriptTriggerReferences(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -209,6 +215,7 @@ public class ValidationService
/// </list>
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with errors and warnings from all expression trigger checks.</returns>
public static ValidationResult ValidateExpressionTriggers(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -302,6 +309,7 @@ public class ValidationService
/// configuration. Returns <c>null</c> on malformed JSON or a missing key.
/// </summary>
/// <param name="triggerConfigJson">The trigger configuration JSON to parse.</param>
/// <returns>The expression string, or <c>null</c> if absent or the JSON is malformed.</returns>
internal static string? ExtractExpressionFromTriggerConfig(string? triggerConfigJson)
{
if (string.IsNullOrWhiteSpace(triggerConfigJson))
@@ -330,6 +338,7 @@ public class ValidationService
/// looks well-formed.
/// </summary>
/// <param name="expression">The expression to check for syntax errors.</param>
/// <returns>A human-readable error message if the expression is invalid; <c>null</c> if well-formed.</returns>
internal static string? CheckExpressionSyntax(string expression)
{
// Advisory forbidden-API scan (TemplateEngine-006): code-region-aware so
@@ -439,6 +448,7 @@ public class ValidationService
/// and skips keys built dynamically.
/// </summary>
/// <param name="expression">The expression to scan for attribute references.</param>
/// <returns>The distinct attribute key strings found in self-attribute accessor positions.</returns>
internal static IEnumerable<string> ExtractAttributeReferences(string expression)
{
var seen = new HashSet<string>(StringComparer.Ordinal);
@@ -490,6 +500,7 @@ public class ValidationService
/// Validates that all data-sourced attributes have connection bindings.
/// </summary>
/// <param name="configuration">The flattened configuration to validate.</param>
/// <returns>A <see cref="ValidationResult"/> with warnings for each data-sourced attribute that lacks a connection binding.</returns>
public static ValidationResult ValidateConnectionBindingCompleteness(FlattenedConfiguration configuration)
{
var errors = new List<ValidationEntry>();
@@ -531,6 +542,7 @@ public class ValidationService
/// Extracts the attribute name from a trigger configuration JSON.
/// </summary>
/// <param name="triggerConfigJson">The trigger configuration JSON to parse.</param>
/// <returns>The attribute name from the <c>attributeName</c> or legacy <c>attribute</c> key, or <c>null</c> if absent or malformed.</returns>
internal static string? ExtractAttributeNameFromTriggerConfig(string triggerConfigJson)
{
// Accept both keys to stay consistent with FlatteningService.PrefixTriggerAttribute,
@@ -558,6 +570,7 @@ public class ValidationService
/// validate" and let other checks surface the deeper problem.
/// </summary>
/// <param name="triggerConfigJson">The trigger configuration JSON to parse.</param>
/// <returns>A <see cref="HiLoSetpoints"/> record with the parsed setpoint values; any missing or non-numeric value is <c>null</c>.</returns>
internal static HiLoSetpoints ExtractHiLoSetpoints(string triggerConfigJson)
{
try