Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/bids/types/tsv.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class BidsTsvFile extends BidsFile {
* @type {Map<string, string[]>}
*/
parsedTsv

/**
* HED strings in the "HED" column of the TSV data.
* @type {string[]}
Expand Down
41 changes: 24 additions & 17 deletions src/bids/validator/sidecarValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ import { BidsValidator } from './validator'
* Validator for HED data in BIDS JSON sidecars.
*/
export class BidsHedSidecarValidator extends BidsValidator {
/**
* The BIDS sidecar being validated.
* @type {BidsSidecar}
*/
sidecar

/**
* Constructor for the BidsHedSidecarValidator.
*
* @param {BidsSidecar} sidecar - The BIDS bidsFile being validated.
* @param {Schemas} hedSchemas - The schemas used for the sidecar validation.
*/
constructor(sidecar, hedSchemas) {
super(sidecar, hedSchemas)
super(hedSchemas)
this.sidecar = sidecar
}

/**
Expand All @@ -24,9 +31,9 @@ export class BidsHedSidecarValidator extends BidsValidator {
*/
validate() {
// Allow schema to be set a validation time -- this is checked by the superclass of BIDS file
const [errorIssues, warningIssues] = this.bidsFile.parseHed(this.hedSchemas)
this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.bidsFile.file))
this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.bidsFile.file))
const [errorIssues, warningIssues] = this.sidecar.parseHed(this.hedSchemas)
this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.sidecar.file))
this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.sidecar.file))
if (errorIssues.length > 0) {
return
}
Expand All @@ -42,7 +49,7 @@ export class BidsHedSidecarValidator extends BidsValidator {
_validateStrings() {
const issues = []

for (const [sidecarKeyName, hedData] of this.bidsFile.parsedHedData) {
for (const [sidecarKeyName, hedData] of this.sidecar.parsedHedData) {
if (hedData instanceof ParsedHedString) {
// Value options have HED as string.
issues.push(...this._checkDetails(sidecarKeyName, hedData))
Expand Down Expand Up @@ -82,37 +89,37 @@ export class BidsHedSidecarValidator extends BidsValidator {
* @private
*/
_checkDefs(sidecarKeyName, hedString, placeholdersAllowed) {
let issues = this.bidsFile.definitions.validateDefs(hedString, this.hedSchemas, placeholdersAllowed)
let issues = this.sidecar.definitions.validateDefs(hedString, this.hedSchemas, placeholdersAllowed)
if (issues.length > 0) {
return BidsHedIssue.fromHedIssues(issues, this.bidsFile.file, { sidecarKeyName: sidecarKeyName })
return BidsHedIssue.fromHedIssues(issues, this.sidecar.file, { sidecarKeyName: sidecarKeyName })
}
issues = this.bidsFile.definitions.validateDefExpands(hedString, this.hedSchemas, placeholdersAllowed)
return BidsHedIssue.fromHedIssues(issues, this.bidsFile.file, { sidecarKeyName: sidecarKeyName })
issues = this.sidecar.definitions.validateDefExpands(hedString, this.hedSchemas, placeholdersAllowed)
return BidsHedIssue.fromHedIssues(issues, this.sidecar.file, { sidecarKeyName: sidecarKeyName })
}

_checkPlaceholders(sidecarKeyName, hedString) {
const numberPlaceholders = getCharacterCount(hedString.hedString, '#')
const sidecarKey = this.bidsFile.sidecarKeys.get(sidecarKeyName)
const sidecarKey = this.sidecar.sidecarKeys.get(sidecarKeyName)
if (!sidecarKey.valueString && !sidecarKey.hasDefinitions && numberPlaceholders > 0) {
return [
BidsHedIssue.fromHedIssue(
generateIssue('invalidSidecarPlaceholder', { column: sidecarKeyName, string: hedString.hedString }),
this.bidsFile.file,
this.sidecar.file,
),
]
} else if (sidecarKey.valueString && numberPlaceholders === 0) {
return [
BidsHedIssue.fromHedIssue(
generateIssue('missingPlaceholder', { column: sidecarKeyName, string: hedString.hedString }),
this.bidsFile.file,
this.sidecar.file,
),
]
}
if (sidecarKey.valueString && numberPlaceholders > 1) {
return [
BidsHedIssue.fromHedIssue(
generateIssue('invalidSidecarPlaceholder', { column: sidecarKeyName, string: hedString.hedString }),
this.bidsFile.file,
this.sidecar.file,
),
]
}
Expand All @@ -126,23 +133,23 @@ export class BidsHedSidecarValidator extends BidsValidator {
*/
_validateCurlyBraces() {
const issues = []
const references = this.bidsFile.columnSpliceMapping
const references = this.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 }),
this.bidsFile.file,
this.sidecar.file,
),
)
}
if (!this.bidsFile.parsedHedData.has(referredKey) && referredKey !== 'HED') {
if (!this.sidecar.parsedHedData.has(referredKey) && referredKey !== 'HED') {
issues.push(
BidsHedIssue.fromHedIssue(
generateIssue('undefinedCurlyBraces', { column: referredKey }),
this.bidsFile.file,
this.sidecar.file,
),
)
}
Expand Down
53 changes: 30 additions & 23 deletions src/bids/validator/tsvValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { EventManager } from '../../parser/eventManager'
* Validator for HED data in BIDS TSV files.
*/
export class BidsHedTsvValidator extends BidsValidator {
/**
* The BIDS TSV file being validated.
* @type {BidsTsvFile}
*/
tsvFile

/**
* The singleton instance of the checker for reserved requirements.
* @type {ReservedChecker}
Expand All @@ -25,7 +31,8 @@ export class BidsHedTsvValidator extends BidsValidator {
* @param {Schemas} hedSchemas - The HED schemas used to validate the tsv file.
*/
constructor(tsvFile, hedSchemas) {
super(tsvFile, hedSchemas)
super(hedSchemas)
this.tsvFile = tsvFile
this.reserved = ReservedChecker.getInstance()
}

Expand All @@ -35,8 +42,8 @@ export class BidsHedTsvValidator extends BidsValidator {
*/
validate() {
// Validate the BIDS bidsFile if it exists and return if there are errors
if (this.bidsFile.mergedSidecar) {
const issues = this.bidsFile.mergedSidecar.validate(this.hedSchemas)
if (this.tsvFile.mergedSidecar) {
const issues = this.tsvFile.mergedSidecar.validate(this.hedSchemas)
const splitErrors = BidsHedIssue.splitErrors(issues)
this.errors.push(...(splitErrors.error ?? []))
this.warnings.push(...(splitErrors.warning ?? []))
Expand All @@ -51,24 +58,24 @@ export class BidsHedTsvValidator extends BidsValidator {
return
}
// Now do a full validation
const bidsHedTsvParser = new BidsHedTsvParser(this.bidsFile, this.hedSchemas)
const bidsHedTsvParser = new BidsHedTsvParser(this.tsvFile, this.hedSchemas)
const [bidsEvents, errorIssues, warningIssues] = bidsHedTsvParser.parse()
this.errors.push(...errorIssues)
this.warnings.push(...warningIssues)
if (this.errors.length > 0) {
return
}
this.validateDataset(bidsEvents)
if (this.errors.length === 0 && this.bidsFile.mergedSidecar?.hasHedData) {
if (this.errors.length === 0 && this.tsvFile.mergedSidecar?.hasHedData) {
this._checkMissingHedWarning()
this._checkMissingValueWarnings()
}
}

_checkMissingHedWarning() {
// Check for HED column used as splice but no HED column
if (this.bidsFile.mergedSidecar.columnSpliceReferences.has('HED') && !this.bidsFile.parsedTsv.has('HED')) {
this.warnings.push(BidsHedIssue.fromHedIssue(generateIssue('hedUsedAsSpliceButNoTsvHed'), this.bidsFile.file))
if (this.tsvFile.mergedSidecar.columnSpliceReferences.has('HED') && !this.tsvFile.parsedTsv.has('HED')) {
this.warnings.push(BidsHedIssue.fromHedIssue(generateIssue('hedUsedAsSpliceButNoTsvHed'), this.tsvFile.file))
}
}

Expand All @@ -77,21 +84,21 @@ export class BidsHedTsvValidator extends BidsValidator {
* @private
*/
_checkMissingValueWarnings() {
for (const columnName of this.bidsFile.parsedTsv.keys()) {
const sidecarColumn = this.bidsFile.mergedSidecar?.sidecarKeys.get(columnName)
for (const columnName of this.tsvFile.parsedTsv.keys()) {
const sidecarColumn = this.tsvFile.mergedSidecar?.sidecarKeys.get(columnName)
if (!sidecarColumn || sidecarColumn.isValueKey) {
continue
}
const toRemove = new Set(['', 'n/a', null, undefined])
const tsvColumnValues = new Set(this.bidsFile.parsedTsv.get(columnName))
const tsvColumnValues = new Set(this.tsvFile.parsedTsv.get(columnName))
const cleanedValues = new Set([...tsvColumnValues].filter((value) => !toRemove.has(value)))
const missingValues = [...cleanedValues].filter((value) => !sidecarColumn.categoryMap.has(value))
if (missingValues.length > 0) {
const values = '[' + missingValues.join(', ') + ']'
this.warnings.push(
BidsHedIssue.fromHedIssue(
generateIssue('sidecarKeyMissing', { column: columnName, values: values }),
this.bidsFile.file,
this.tsvFile.file,
),
)
}
Expand All @@ -104,8 +111,8 @@ export class BidsHedTsvValidator extends BidsValidator {
* @private
*/
_validateHedColumn() {
if (this.bidsFile.hedColumnHedStrings.length > 0) {
this.bidsFile.hedColumnHedStrings.flatMap((hedString, rowIndexMinusTwo) =>
if (this.tsvFile.hedColumnHedStrings.length > 0) {
this.tsvFile.hedColumnHedStrings.flatMap((hedString, rowIndexMinusTwo) =>
this._validateHedColumnString(hedString, rowIndexMinusTwo + 2),
)
}
Expand All @@ -125,8 +132,8 @@ export class BidsHedTsvValidator extends BidsValidator {

// Find basic parsing issues and return if unable to parse the string. (Warnings are okay.)
const [parsedString, errorIssues, warningIssues] = parseHedString(hedString, this.hedSchemas, false, false)
this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.bidsFile.file, { tsvLine: rowIndex }))
this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.bidsFile.file, { tsvLine: rowIndex }))
this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.tsvFile.file, { tsvLine: rowIndex }))
this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.tsvFile.file, { tsvLine: rowIndex }))
if (parsedString === null) {
return
}
Expand All @@ -139,18 +146,18 @@ export class BidsHedTsvValidator extends BidsValidator {
string: parsedString.hedString,
tsvLine: rowIndex.toString(),
}),
this.bidsFile.file,
this.tsvFile.file,
),
)
return
}

// Check whether definitions used exist and are used correctly.
const defIssues = [
...this.bidsFile.mergedSidecar.definitions.validateDefs(parsedString, this.hedSchemas, false),
...this.bidsFile.mergedSidecar.definitions.validateDefExpands(parsedString, this.hedSchemas, false),
...this.tsvFile.mergedSidecar.definitions.validateDefs(parsedString, this.hedSchemas, false),
...this.tsvFile.mergedSidecar.definitions.validateDefExpands(parsedString, this.hedSchemas, false),
]
this.errors.push(...BidsHedIssue.fromHedIssues(defIssues, this.bidsFile.file, { tsvLine: rowIndex }))
this.errors.push(...BidsHedIssue.fromHedIssues(defIssues, this.tsvFile.file, { tsvLine: rowIndex }))
}

/**
Expand All @@ -163,7 +170,7 @@ export class BidsHedTsvValidator extends BidsValidator {
return
}
// Temporal files have to check Onset, Inset, Offset consistency.
if (this.bidsFile.isTimelineFile) {
if (this.tsvFile.isTimelineFile) {
this._validateTemporal(elements)
} else {
// Non-temporal files cannot have temporal tags.
Expand Down Expand Up @@ -213,8 +220,8 @@ export class BidsHedTsvValidator extends BidsValidator {
const rowString = elementList.map((element) => element.hedString).join(',')
const [parsedString, errorIssues, warningIssues] = parseHedString(rowString, this.hedSchemas, false, false)
const tsvLines = BidsTsvElement.getTsvLines(elementList)
this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.bidsFile.file, { tsvLine: tsvLines }))
this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.bidsFile.file, { tsvLine: tsvLines }))
this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.tsvFile.file, { tsvLine: tsvLines }))
this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.tsvFile.file, { tsvLine: tsvLines }))
}
}

Expand Down Expand Up @@ -273,7 +280,7 @@ export class BidsHedTsvValidator extends BidsValidator {
this.errors.push(
BidsHedIssue.fromHedIssue(
generateIssue('temporalTagInNonTemporalContext', { string: element.hedString, tsvLine: element.tsvLine }),
this.bidsFile.file,
this.tsvFile.file,
),
)
}
Expand Down
17 changes: 5 additions & 12 deletions src/bids/validator/validator.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/**
* Validator base class for HED data in BIDS TSV files.
* @abstract
*/
export class BidsValidator {
/**
* The BIDS file being validated.
* @type {BidsFile}
*/
bidsFile
/**
* The HED schema collection being validated against.
* @type {Schemas}
*/
hedSchemas

/**
* The issues found during validation.
* @type {BidsHedIssue[]}
Expand All @@ -27,21 +24,17 @@ export class BidsValidator {
/**
* Bids validator base class.
*
* @param {BidsFile} bidsFile - The BIDS TSV file being validated.
* @param {Schemas} hedSchemas - The HED schemas used for validation.
*/
constructor(bidsFile, hedSchemas) {
this.bidsFile = bidsFile
constructor(hedSchemas) {
this.hedSchemas = hedSchemas // Will be set when the file is validated
this.errors = []
this.warnings = []
}

/**
* Validate a BIDS file. Overridden by particular types of BIDS files.
*
* @abstract
*/
validate() {
return
}
validate() {}
}
6 changes: 6 additions & 0 deletions src/issues/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,15 @@ export default {
level: 'error',
message: stringTemplate`The key 'HED' was illegally used within a non-HED sidecar column.`,
},
// Internal errors
internalError: {
hedCode: 'INTERNAL_ERROR',
level: 'error',
message: stringTemplate`Internal error - message: "${'message'}".`,
},
genericError: {
hedCode: 'INTERNAL_ERROR',
level: 'error',
message: stringTemplate`Unknown HED error "${'internalCode'}" - parameters: "${'parameters'}".`,
},
}
Loading
Loading