All files / transpiler/output/codegen/helpers MemberAccessValidator.ts

100% Statements 13/13
100% Branches 14/14
100% Functions 3/3
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                  9x 1x   8x 8x 2x                                   28x 2x                                             93x 17x   76x 26x 26x 4x                  
/**
 * MemberAccessValidator - Shared validation for member access patterns
 *
 * Extracted from CodeGenerator.generateMemberAccess() and PostfixExpressionGenerator
 * to eliminate cross-file duplication of:
 * - Write-only register member checks (ADR-013)
 * - Self-referential scope access checks (ADR-016/057)
 */
 
class MemberAccessValidator {
  /**
   * ADR-013: Validate that a register member is not write-only when reading.
   * @param registerKey - underscore-joined key (e.g., "GPIO7_DR")
   * @param memberName - member being accessed (e.g., "DR")
   * @param displayName - dot-notation for error (e.g., "GPIO7.DR")
   * @param registerMemberAccess - map of register member access modifiers
   * @param isAssignmentTarget - true to skip check (write context)
   */
  static validateRegisterReadAccess(
    registerKey: string,
    memberName: string,
    displayName: string,
    registerMemberAccess: ReadonlyMap<string, string>,
    isAssignmentTarget: boolean,
  ): void {
    if (isAssignmentTarget) {
      return;
    }
    const accessMod = registerMemberAccess.get(registerKey);
    if (accessMod === "wo") {
      throw new Error(
        `cannot read from write-only register member '${memberName}' ` +
          `(${displayName} has 'wo' access modifier)`,
      );
    }
  }
 
  /**
   * ADR-016/057: Validate not referencing own scope by name.
   * @param scopeName - scope being accessed
   * @param memberName - member after scope (for error message)
   * @param currentScope - active scope context (null = not in a scope)
   */
  static validateNotSelfScopeReference(
    scopeName: string,
    memberName: string,
    currentScope: string | null,
  ): void {
    if (currentScope && scopeName === currentScope) {
      throw new Error(
        `Error: Cannot reference own scope '${scopeName}' by name. Use 'this.${memberName}' instead of '${scopeName}.${memberName}'`,
      );
    }
  }
 
  /**
   * ADR-016/057: Validate that a global entity (enum or register) is accessed
   * with 'global.' prefix when inside a scope that doesn't own the entity.
   *
   * @param entityName - The entity being accessed (e.g., "Color", "GPIO")
   * @param memberName - The member after the entity (e.g., "Red", "PIN0")
   * @param entityType - "enum" or "register" (for error message)
   * @param currentScope - Active scope context (null = not in a scope)
   * @param isGlobalAccess - Whether the access used 'global.' prefix
   */
  static validateGlobalEntityAccess(
    entityName: string,
    memberName: string,
    entityType: string,
    currentScope: string | null,
    isGlobalAccess: boolean,
  ): void {
    if (isGlobalAccess) {
      return;
    }
    if (currentScope) {
      const belongsToCurrentScope = entityName.startsWith(currentScope + "_");
      if (!belongsToCurrentScope) {
        throw new Error(
          `Error: Use 'global.${entityName}.${memberName}' to access ${entityType} '${entityName}' from inside scope '${currentScope}'`,
        );
      }
    }
  }
}
 
export default MemberAccessValidator;