hed-standard/hed-javascript

View on GitHub
bids/validator/bidsHedSidecarValidator.js

Summary

Maintainability
A
1 hr
Test Coverage
A
94%
import { BidsHedIssue } from '../types/issues'
import ParsedHedString from '../../parser/parsedHedString'
// IMPORTANT: This import cannot be shortened to '../../validator', as this creates a circular dependency until v4.0.0.
import { validateHedString } from '../../validator/event/init'
import { generateIssue } from '../../common/issues/issues'

/**
 * Validator for HED data in BIDS JSON sidecars.
 */
export class BidsHedSidecarValidator {
  /**
   * The BIDS dataset being validated.
   * @type {BidsDataset}
   */
  dataset
  /**
   * The HED schema collection being validated against.
   * @type {Schemas}
   */
  hedSchemas
  /**
   * The issues found during validation.
   * @type {BidsHedIssue[]}
   */
  issues

  /**
   * Constructor.
   *
   * @param {BidsDataset} dataset The BIDS dataset being validated.
   * @param {Schemas} hedSchemas The HED schema collection being validated against.
   */
  constructor(dataset, hedSchemas) {
    this.dataset = dataset
    this.hedSchemas = hedSchemas
    this.issues = []
  }

  /**
   * Validate a collection of BIDS sidecars. This method returns the complete issue list for convenience.
   *
   * @returns {BidsHedIssue[]} Any issues found during validation of the sidecars.
   */
  validateSidecars() {
    for (const sidecar of this.dataset.sidecarData) {
      const sidecarParsingIssues = BidsHedIssue.fromHedIssues(sidecar.parseHedStrings(this.hedSchemas), sidecar.file)
      this.issues.push(...sidecarParsingIssues)
      if (sidecarParsingIssues.length > 0) {
        break
      }
      const sidecarIssues = this.validateSidecar(sidecar)
      this.issues.push(...sidecarIssues)
    }
    return this.issues
  }

  /**
   * Validate an individual BIDS sidecar.
   *
   * @param {BidsSidecar} sidecar A BIDS sidecar.
   * @returns {BidsHedIssue[]} All issues found.
   */
  validateSidecar(sidecar) {
    return [...this._validateSidecarStrings(sidecar), ...BidsHedSidecarValidator.validateSidecarCurlyBraces(sidecar)]
  }

  /**
   * Validate an individual BIDS sidecar's HED strings.
   *
   * @param {BidsSidecar} sidecar A BIDS sidecar.
   * @returns {BidsHedIssue[]} All issues found.
   */
  _validateSidecarStrings(sidecar) {
    const issues = []

    const categoricalOptions = {
      checkForWarnings: true,
      expectValuePlaceholderString: false,
      definitionsAllowed: 'exclusive',
    }
    const valueOptions = {
      checkForWarnings: true,
      expectValuePlaceholderString: true,
      definitionsAllowed: 'no',
    }

    for (const [sidecarKey, hedData] of sidecar.parsedHedData) {
      if (hedData instanceof ParsedHedString) {
        issues.push(...this._validateSidecarString(sidecarKey, hedData, sidecar, valueOptions))
      } else if (hedData instanceof Map) {
        for (const valueString of hedData.values()) {
          issues.push(...this._validateSidecarString(sidecarKey, valueString, sidecar, categoricalOptions))
        }
      } else {
        throw new Error('Unexpected type found in sidecar parsedHedData map.')
      }
    }

    return issues
  }

  /**
   * Validate an individual BIDS sidecar string.
   *
   * @param {string} sidecarKey The sidecar key this string belongs to.
   * @param {ParsedHedString} sidecarString The parsed sidecar HED string.
   * @param {BidsSidecar} sidecar The BIDS sidecar.
   * @param {Object} options Options specific to this validation run to pass to {@link validateHedString}.
   * @returns {BidsHedIssue[]} All issues found.
   * @private
   */
  _validateSidecarString(sidecarKey, sidecarString, sidecar, options) {
    // Parsing issues already pushed in validateSidecars()
    if (sidecarString === null) {
      return []
    }

    const [, hedIssues] = validateHedString(sidecarString, this.hedSchemas, options)
    return BidsHedIssue.fromHedIssues(hedIssues, sidecar.file, { sidecarKey })
  }

  /**
   * Validate an individual BIDS sidecar's curly braces.
   *
   * @param {BidsSidecar} sidecar A BIDS sidecar.
   * @returns {BidsHedIssue[]} All issues found.
   */
  static validateSidecarCurlyBraces(sidecar) {
    const issues = []
    const references = sidecar.columnSpliceMapping

    for (const [key, referredKeys] of references) {
      for (const referredKey of referredKeys) {
        if (references.has(referredKey)) {
          issues.push(
            BidsHedIssue.fromHedIssue(
              generateIssue('recursiveCurlyBracesWithKey', { column: referredKey, referrer: key }),
              sidecar.file,
            ),
          )
        }
      }
    }

    return issues
  }
}