All files / packages/fds-uif/core/src errors.ts

100% Statements 55/55
100% Branches 6/6
100% Functions 0/0
100% Lines 55/55

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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298                          28x   28x 28x     28x                                       4x   4x   4x   4x                                                 2x   2x                                                 2x   2x   2x   2x   2x   2x         1x     1x     2x                                     4x   4x   4x   4x                               2x   2x   2x   2x   2x                                               6x   6x   6x   6x     6x         2x     6x                                             4x   4x   4x   4x   4x   4x   4x     4x       1x 1x 1x   3x     4x   4x                                         4x   4x   4x   4x   4x 4x                
/**
 * @fds-uif/core - Error Classes
 *
 * Custom error types for UIF processing operations.
 * All errors are designed to be informative with clear paths to resolution.
 */
 
/**
 * Base class for all UIF-related errors.
 */
export abstract class UifError extends Error {
  constructor(
    message: string,
    public readonly code: string
  ) {
    super(message);
    this.name = this.constructor.name;
    // Maintains proper stack trace for where the error was thrown (V8 engines)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}
 
/**
 * Error thrown when the `$extends` reference cannot be resolved.
 *
 * @example
 * ```ts
 * throw new UifExtensionError(
 *   './badge.slds.uif.json',
 *   './badge.foundation.uif.json',
 *   'File not found'
 * );
 * ```
 */
export class UifExtensionError extends UifError {
  constructor(
    /** The file containing the `$extends` reference */
    public readonly sourceFile: string,
    /** The path that could not be resolved */
    public readonly basePath: string,
    /** Additional details about the failure */
    public readonly reason: string
  ) {
    super(
      `Failed to resolve $extends in "${sourceFile}"\n` +
        `  Base file: "${basePath}"\n` +
        `  Reason: ${reason}`,
      'UIF_EXTENSION_ERROR'
    );
  }
}
 
/**
 * Error thrown when a circular `$extends` chain is detected.
 *
 * @example
 * ```ts
 * throw new UifCircularDependencyError([
 *   './a.uif.json',
 *   './b.uif.json',
 *   './c.uif.json',
 *   './a.uif.json'
 * ]);
 * ```
 */
export class UifCircularDependencyError extends UifError {
  constructor(
    /** The chain of files that form the circular dependency */
    public readonly chain: string[]
  ) {
    super(
      `Circular $extends dependency detected\n` +
        `  Chain: ${chain.join(' → ')}`,
      'UIF_CIRCULAR_DEPENDENCY'
    );
  }
}
 
/**
 * Error thrown when merge encounters incompatible types.
 *
 * @example
 * ```ts
 * throw new UifMergeError(
 *   'structure.restrict',
 *   'array',
 *   'string',
 *   ['span'],
 *   'div'
 * );
 * ```
 */
export class UifMergeError extends UifError {
  constructor(
    /** JSON path where the conflict occurred */
    public readonly path: string,
    /** Expected type from the base/foundation */
    public readonly expectedType: string,
    /** Actual type from the extending/system */
    public readonly receivedType: string,
    /** The value from the base */
    public readonly baseValue?: unknown,
    /** The value from the extension */
    public readonly extensionValue?: unknown
  ) {
    let message = `Type mismatch at "${path}"\n` +
      `  Expected: ${expectedType}\n` +
      `  Received: ${receivedType}`;
    
    if (baseValue !== undefined) {
      message += `\n  Base value: ${JSON.stringify(baseValue)}`;
    }
    if (extensionValue !== undefined) {
      message += `\n  Extension value: ${JSON.stringify(extensionValue)}`;
    }
    
    super(message, 'UIF_MERGE_ERROR');
  }
}
 
/**
 * Error thrown when duplicate identifiers are found in an identified array.
 *
 * @example
 * ```ts
 * throw new UifDuplicateIdentifierError(
 *   'structure.children',
 *   'name',
 *   'icon'
 * );
 * ```
 */
export class UifDuplicateIdentifierError extends UifError {
  constructor(
    /** JSON path to the array containing duplicates */
    public readonly path: string,
    /** The identifier field (e.g., 'name', 'id', 'state') */
    public readonly identifierField: string,
    /** The duplicated value */
    public readonly duplicateValue: string
  ) {
    super(
      `Duplicate identifier in "${path}"\n` +
        `  Field: ${identifierField}\n` +
        `  Duplicate value: "${duplicateValue}"`,
      'UIF_DUPLICATE_IDENTIFIER'
    );
  }
}
 
/**
 * Error thrown when a positional marker ($before/$after) references
 * a non-existent sibling.
 */
export class UifPositionalMarkerError extends UifError {
  constructor(
    /** JSON path to the array */
    public readonly path: string,
    /** The marker type ('$before' or '$after') */
    public readonly marker: '$before' | '$after',
    /** The sibling name that was referenced */
    public readonly referencedSibling: string,
    /** Available sibling names */
    public readonly availableSiblings: string[]
  ) {
    super(
      `Invalid positional marker at "${path}"\n` +
        `  ${marker}: "${referencedSibling}" not found\n` +
        `  Available: ${availableSiblings.length > 0 ? availableSiblings.join(', ') : '(none)'}`,
      'UIF_POSITIONAL_MARKER_ERROR'
    );
  }
}
 
/**
 * Error thrown when stateClasses reference non-existent states.
 *
 * @example
 * ```ts
 * throw new UifInvalidStateReferenceError(
 *   'stateClasses[0]',
 *   'actve',
 *   ['active', 'disabled']
 * );
 * ```
 */
export class UifInvalidStateReferenceError extends UifError {
  constructor(
    /** JSON path to the invalid reference */
    public readonly path: string,
    /** The invalid state reference */
    public readonly invalidState: string,
    /** Available state names */
    public readonly availableStates: string[],
    /** Optional suggestion for typo correction */
    public readonly suggestion?: string
  ) {
    let message =
      `Invalid state reference at "${path}"\n` +
      `  Referenced: "${invalidState}"\n` +
      `  Available: ${availableStates.length > 0 ? availableStates.join(', ') : '(none)'}`;
 
    if (suggestion) {
      message += `\n\n  Did you mean "${suggestion}"?`;
    }
 
    super(message, 'UIF_INVALID_STATE_REFERENCE');
  }
}
 
/**
 * Error thrown when variants have conflicting structural changes.
 *
 * @example
 * ```ts
 * throw new UifVariantConflictError(
 *   'structure.variants',
 *   'icon-only',
 *   'with-badge',
 *   'restrict',
 *   'icon',
 *   ['svg'],
 *   ['span']
 * );
 * ```
 */
export class UifVariantConflictError extends UifError {
  constructor(
    /** JSON path to the variants array */
    public readonly path: string,
    /** First conflicting variant */
    public readonly variant1: string,
    /** Second conflicting variant */
    public readonly variant2: string,
    /** The property with conflicting values */
    public readonly conflictProperty: string,
    /** The child name where conflict occurs (if applicable) */
    public readonly childName?: string,
    /** Value from first variant */
    public readonly value1?: unknown,
    /** Value from second variant */
    public readonly value2?: unknown
  ) {
    let message =
      `Conflicting variant changes at "${path}"\n` +
      `  Variants "${variant1}" and "${variant2}" both modify`;
 
    if (childName) {
      message += ` child "${childName}":\n`;
      message += `    - ${variant1} sets ${conflictProperty}: ${JSON.stringify(value1)}\n`;
      message += `    - ${variant2} sets ${conflictProperty}: ${JSON.stringify(value2)}`;
    } else {
      message += ` ${conflictProperty} with incompatible values`;
    }
 
    message += `\n\n  Resolution: Use separate child names or ensure variants are mutually exclusive.`;
 
    super(message, 'UIF_VARIANT_CONFLICT');
  }
}
 
/**
 * Error thrown when an item has conflicting positional markers.
 * An item cannot have both $before and $after markers.
 *
 * @example
 * ```ts
 * throw new UifConflictingMarkersError(
 *   'structure.children[2]',
 *   'icon',
 *   'label',
 *   'content'
 * );
 * ```
 */
export class UifConflictingMarkersError extends UifError {
  constructor(
    /** JSON path to the item */
    public readonly path: string,
    /** The item identifier (if available) */
    public readonly itemIdentifier: string | undefined,
    /** The $before value */
    public readonly beforeValue: string,
    /** The $after value */
    public readonly afterValue: string
  ) {
    const idPart = itemIdentifier ? ` (${itemIdentifier})` : '';
    super(
      `Conflicting positional markers at "${path}"${idPart}\n` +
        `  Item has both $before: "${beforeValue}" and $after: "${afterValue}"\n` +
        `  An item can only have one positional marker.`,
      'UIF_CONFLICTING_MARKERS'
    );
  }
}