diff --git a/bids/schema.js b/bids/schema.js index e76f629b..24fd118f 100644 --- a/bids/schema.js +++ b/bids/schema.js @@ -45,7 +45,7 @@ function validateSchemasSpec(schemasSpec) { if (schemasSpec instanceof SchemasSpec) { return schemasSpec } else { - throw new IssueError(generateIssue('invalidSchemaSpecification', { spec: JSON.stringify(schemasSpec) })) + IssueError.generateAndThrow('invalidSchemaSpecification', { spec: JSON.stringify(schemasSpec) }) } } @@ -70,12 +70,12 @@ function splitNicknameAndSchema(schemaVersion) { let nickname = '' let schema if (nicknameSplit.length > 2) { - throw new IssueError(generateIssue('invalidSchemaSpecification', { spec: schemaVersion })) + IssueError.generateAndThrow('invalidSchemaSpecification', { spec: schemaVersion }) } if (nicknameSplit.length > 1) { ;[nickname, schema] = nicknameSplit if (!alphabeticRegExp.test(nickname)) { - throw new IssueError(generateIssue('invalidSchemaNickname', { nickname: nickname, spec: schemaVersion })) + IssueError.generateAndThrow('invalidSchemaNickname', { nickname: nickname, spec: schemaVersion }) } } else { schema = nicknameSplit[0] @@ -88,18 +88,18 @@ function splitLibraryAndVersion(schemaVersion, originalVersion) { let library = '' let version if (versionSplit.length > 2) { - throw new IssueError(generateIssue('invalidSchemaSpecification', { spec: originalVersion })) + IssueError.generateAndThrow('invalidSchemaSpecification', { spec: originalVersion }) } if (versionSplit.length > 1) { ;[library, version] = versionSplit if (!alphabeticRegExp.test(library)) { - throw new IssueError(generateIssue('invalidSchemaSpecification', { spec: originalVersion })) + IssueError.generateAndThrow('invalidSchemaSpecification', { spec: originalVersion }) } } else { version = versionSplit[0] } if (!semver.valid(version)) { - throw new IssueError(generateIssue('invalidSchemaSpecification', { spec: originalVersion })) + IssueError.generateAndThrow('invalidSchemaSpecification', { spec: originalVersion }) } return [library, version] } diff --git a/bids/types/json.js b/bids/types/json.js index 3aad6193..36da7826 100644 --- a/bids/types/json.js +++ b/bids/types/json.js @@ -5,7 +5,7 @@ import { parseHedString } from '../../parser/main' import ParsedHedString from '../../parser/parsedHedString' import { BidsFile } from './basic' import BidsHedSidecarValidator from '../validator/bidsHedSidecarValidator' -import { generateIssue, IssueError } from '../../common/issues/issues' +import { IssueError } from '../../common/issues/issues' const ILLEGAL_SIDECAR_KEYS = new Set(['hed', 'n/a']) @@ -81,7 +81,7 @@ export class BidsSidecar extends BidsJsonFile { .map(([sidecarKey, sidecarValue]) => { const trimmedSidecarKey = sidecarKey.trim() if (ILLEGAL_SIDECAR_KEYS.has(trimmedSidecarKey.toLowerCase())) { - throw new IssueError(generateIssue('illegalSidecarHedKey', {})) + IssueError.generateAndThrow('illegalSidecarHedKey') } if (sidecarValueHasHed(sidecarValue)) { return [trimmedSidecarKey, new BidsSidecarKey(trimmedSidecarKey, sidecarValue.HED, this)] @@ -104,7 +104,7 @@ export class BidsSidecar extends BidsJsonFile { */ _verifyKeyHasNoDeepHed(key, value) { if (key.toUpperCase() === 'HED') { - throw new IssueError(generateIssue('illegalSidecarHedDeepKey', {})) + IssueError.generateAndThrow('illegalSidecarHedDeepKey') } if (!isPlainObject(value)) { return @@ -206,9 +206,9 @@ export class BidsSidecar extends BidsJsonFile { this.columnSpliceMapping.set(sidecarKey, keyReferences) } } else { - throw new IssueError( - generateIssue('internalConsistencyError', { message: 'Unexpected type found in sidecar parsedHedData map.' }), - ) + IssueError.generateAndThrow('internalConsistencyError', { + message: 'Unexpected type found in sidecar parsedHedData map.', + }) } } } @@ -275,7 +275,7 @@ export class BidsSidecarKey { if (typeof data === 'string') { this.valueString = data } else if (!isPlainObject(data)) { - throw new IssueError(generateIssue('illegalSidecarHedType', { key: key, file: sidecar.file.relativePath })) + IssueError.generateAndThrow('illegalSidecarHedType', { key: key, file: sidecar.file.relativePath }) } else { this.categoryMap = data } @@ -307,11 +307,12 @@ export class BidsSidecarKey { for (const [value, string] of Object.entries(this.categoryMap)) { const trimmedValue = value.trim() if (ILLEGAL_SIDECAR_KEYS.has(trimmedValue.toLowerCase())) { - throw new IssueError(generateIssue('illegalSidecarHedCategoricalValue', {})) + IssueError.generateAndThrow('illegalSidecarHedCategoricalValue') } else if (typeof string !== 'string') { - throw new IssueError( - generateIssue('illegalSidecarHedType', { key: value, file: this.sidecar.deref()?.file?.relativePath }), - ) + IssueError.generateAndThrow('illegalSidecarHedType', { + key: value, + file: this.sidecar.deref()?.file?.relativePath, + }) } const [parsedString, parsingIssues] = parseHedString(string, hedSchemas) this.parsedCategoryMap.set(value, parsedString) diff --git a/bids/types/tsv.js b/bids/types/tsv.js index 73ca5da7..e4277cbc 100644 --- a/bids/types/tsv.js +++ b/bids/types/tsv.js @@ -5,7 +5,7 @@ import { convertParsedTSVData, parseTSV } from '../tsvParser' import { BidsSidecar } from './json' import ParsedHedString from '../../parser/parsedHedString' import BidsHedTsvValidator from '../validator/bidsHedTsvValidator' -import { generateIssue, IssueError } from '../../common/issues/issues' +import { IssueError } from '../../common/issues/issues' /** * A BIDS TSV file. @@ -58,7 +58,7 @@ export class BidsTsvFile extends BidsFile { } else if (isPlainObject(tsvData)) { this.parsedTsv = convertParsedTSVData(tsvData) } else { - throw new IssueError(generateIssue('internalError', { message: 'parsedTsv has an invalid type' })) + IssueError.generateAndThrow('internalError', { message: 'parsedTsv has an invalid type' }) } this.potentialSidecars = potentialSidecars @@ -196,9 +196,9 @@ export class BidsTsvRow extends ParsedHedString { get onset() { const value = Number(this.rowCells.get('onset')) if (Number.isNaN(value)) { - throw new IssueError( - generateIssue('internalError', { message: 'Attempting to access the onset of a TSV row without one.' }), - ) + IssueError.generateAndThrow('internalError', { + message: 'Attempting to access the onset of a TSV row without one.', + }) } return value } diff --git a/bids/validator/bidsHedSidecarValidator.js b/bids/validator/bidsHedSidecarValidator.js index c78669f6..74b2fca0 100644 --- a/bids/validator/bidsHedSidecarValidator.js +++ b/bids/validator/bidsHedSidecarValidator.js @@ -2,7 +2,7 @@ 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' +import { generateIssue, IssueError } from '../../common/issues/issues' /** * Validator for HED data in BIDS JSON sidecars. @@ -81,7 +81,9 @@ export class BidsHedSidecarValidator { issues.push(...this._validateString(sidecarKey, valueString, categoricalOptions)) } } else { - throw new Error('Unexpected type found in sidecar parsedHedData map.') + IssueError.generateAndThrow('internalConsistencyError', { + message: 'Unexpected type found in sidecar parsedHedData map.', + }) } } diff --git a/common/issues/issues.js b/common/issues/issues.js index 4e2dc61a..a54e1bf2 100644 --- a/common/issues/issues.js +++ b/common/issues/issues.js @@ -24,6 +24,17 @@ export class IssueError extends Error { Object.setPrototypeOf(this, IssueError.prototype) } + + /** + * Generate a new {@link Issue} object and immediately throw it as an {@link IssueError}. + * + * @param {string} internalCode The internal error code. + * @param {Object?} parameters The error string parameters. + * @throws {IssueError} Corresponding to the generated {@link Issue}. + */ + static generateAndThrow(internalCode, parameters = {}) { + throw new IssueError(generateIssue(internalCode, parameters)) + } } /** diff --git a/common/schema/loader.js b/common/schema/loader.js index 63c628b8..ba58e39b 100644 --- a/common/schema/loader.js +++ b/common/schema/loader.js @@ -4,7 +4,7 @@ import xml2js from 'xml2js' import * as files from '../../utils/files' -import { generateIssue, IssueError } from '../issues/issues' +import { IssueError } from '../issues/issues' import { localSchemaList } from './config' @@ -18,7 +18,7 @@ import { localSchemaList } from './config' export default async function loadSchema(schemaDef = null) { const xmlData = await loadPromise(schemaDef) if (xmlData === null) { - throw new IssueError(generateIssue('invalidSchemaSpecification', { spec: JSON.stringify(schemaDef) })) + IssueError.generateAndThrow('invalidSchemaSpecification', { spec: JSON.stringify(schemaDef) }) } return xmlData } @@ -82,7 +82,7 @@ async function loadBundledSchema(schemaDef) { return parseSchemaXML(localSchemaList.get(schemaDef.localName)) } catch (error) { const issueArgs = { spec: JSON.stringify(schemaDef), error: error.message } - throw new IssueError(generateIssue('bundledSchemaLoadFailed', issueArgs)) + IssueError.generateAndThrow('bundledSchemaLoadFailed', issueArgs) } } @@ -101,7 +101,7 @@ async function loadSchemaFile(xmlDataPromise, issueCode, issueArgs) { return parseSchemaXML(data) } catch (error) { issueArgs.error = error.message - throw new IssueError(generateIssue(issueCode, issueArgs)) + IssueError.generateAndThrow(issueCode, issueArgs) } } diff --git a/parser/converter.js b/parser/converter.js index eafa3f5d..bf8e2b7c 100644 --- a/parser/converter.js +++ b/parser/converter.js @@ -80,7 +80,7 @@ export default class TagConverter { const firstLevel = this.tagLevels[0].toLowerCase().trimStart() const schemaTag = this.tagMapping.getEntry(firstLevel) if (!schemaTag || firstLevel === '' || firstLevel !== firstLevel.trim()) { - throw new IssueError(generateIssue('invalidTag', { tag: this.tagString })) + IssueError.generateAndThrow('invalidTag', { tag: this.tagString }) } if (this.tagLevels.length === 1) { return schemaTag @@ -109,27 +109,21 @@ export default class TagConverter { _validateChildTag(parentTag, i) { const childTag = this._getSchemaTag(i) if (this.schemaTag instanceof SchemaValueTag) { - throw new IssueError( - generateIssue('internalConsistencyError', { - message: 'Child tag is a value tag which should have been handled earlier.', - }), - ) + IssueError.generateAndThrow('internalConsistencyError', { + message: 'Child tag is a value tag which should have been handled earlier.', + }) } if (childTag === undefined && parentTag && !parentTag.hasAttributeName('extensionAllowed')) { - throw new IssueError( - generateIssue('invalidExtension', { - tag: this.tagLevels[i], - parentTag: parentTag.longName, - }), - ) + IssueError.generateAndThrow('invalidExtension', { + tag: this.tagLevels[i], + parentTag: parentTag.longName, + }) } if (childTag !== undefined && (childTag.parent === undefined || childTag.parent !== parentTag)) { - throw new IssueError( - generateIssue('invalidParentNode', { - tag: this.tagLevels[i], - parentTag: childTag.longName, - }), - ) + IssueError.generateAndThrow('invalidParentNode', { + tag: this.tagLevels[i], + parentTag: childTag.longName, + }) } return childTag } @@ -137,7 +131,7 @@ export default class TagConverter { _getSchemaTag(i) { const tagLevel = this.tagLevels[i].toLowerCase() if (tagLevel === '' || tagLevel !== tagLevel.trim()) { - throw new IssueError(generateIssue('invalidTag', { tag: this.tagString })) + IssueError.generateAndThrow('invalidTag', { tag: this.tagString }) } return this.tagMapping.getEntry(tagLevel) } diff --git a/parser/parsedHedGroup.js b/parser/parsedHedGroup.js index 969fecf2..0eb0c8eb 100644 --- a/parser/parsedHedGroup.js +++ b/parser/parsedHedGroup.js @@ -344,12 +344,10 @@ export class ParsedHedGroup extends ParsedHedSubstring { this.defTags.map((defTag) => ParsedHedGroup.findDefinitionName(defTag.canonicalTag, 'Def')), ) } else if (this.defCount > 1) { - throw new IssueError( - generateIssue('temporalWithMultipleDefinitions', { - tagGroup: this.originalTag, - tag: this.temporalGroupName, - }), - ) + IssueError.generateAndThrow('temporalWithMultipleDefinitions', { + tagGroup: this.originalTag, + tag: this.temporalGroupName, + }) } else if (this.hasDefExpandChildren) { return this.defExpandChildren[0].defExpandName } @@ -371,12 +369,10 @@ export class ParsedHedGroup extends ParsedHedSubstring { this.defTags.map((defTag) => ParsedHedGroup.getDefinitionTagValue(defTag, 'Def')), ) } else if (this.defCount > 1) { - throw new IssueError( - generateIssue('temporalWithMultipleDefinitions', { - tagGroup: this.originalTag, - tag: this.temporalGroupName, - }), - ) + IssueError.generateAndThrow('temporalWithMultipleDefinitions', { + tagGroup: this.originalTag, + tag: this.temporalGroupName, + }) } else if (this.hasDefExpandChildren) { return this.defExpandChildren[0].defExpandValue } diff --git a/utils/types.js b/utils/types.js index dbe7ed4c..e4ec089d 100644 --- a/utils/types.js +++ b/utils/types.js @@ -1,6 +1,6 @@ /** Utility classes. **/ -import { generateIssue, IssueError } from '../common/issues/issues' +import { IssueError } from '../common/issues/issues' /** * Superclass for property memoization until we can get away with private fields. @@ -32,9 +32,9 @@ export class Memoizer { */ _memoize(propertyName, valueComputer) { if (!propertyName) { - throw new IssueError( - generateIssue('internalConsistencyError', { message: 'Invalid property name in Memoizer subclass.' }), - ) + IssueError.generateAndThrow('internalConsistencyError', { + message: 'Invalid property name in Memoizer subclass.', + }) } if (this._memoizedProperties.has(propertyName)) { return this._memoizedProperties.get(propertyName) diff --git a/validator/schema/hed3.js b/validator/schema/hed3.js index aaece20f..12e55026 100644 --- a/validator/schema/hed3.js +++ b/validator/schema/hed3.js @@ -257,7 +257,7 @@ export class Hed3SchemaParser extends SchemaParser { const tagEntries = new Map() for (const [name, valueAttributes] of valueAttributeDefinitions) { if (tagEntries.has(name)) { - throw new IssueError(generateIssue('duplicateTagsInSchema', {})) + IssueError.generateAndThrow('duplicateTagsInSchema') } const booleanAttributes = booleanAttributeDefinitions.get(name) const unitClasses = tagUnitClassDefinitions.get(name) @@ -422,13 +422,14 @@ export class Hed3PartneredSchemaMerger { */ _validate(source, destination) { if (source.generation < 3 || destination.generation < 3) { - throw new Error('Partnered schemas must be HED-3G schemas') + IssueError.generateAndThrow('internalConsistencyError', { message: 'Partnered schemas must be HED-3G schemas' }) } if (source.withStandard !== destination.withStandard) { - throw new IssueError( - generateIssue('differentWithStandard', { first: source.withStandard, second: destination.withStandard }), - ) + IssueError.generateAndThrow('differentWithStandard', { + first: source.withStandard, + second: destination.withStandard, + }) } } @@ -482,14 +483,14 @@ export class Hed3PartneredSchemaMerger { const shortName = tag.name if (this.destinationTags.hasEntry(shortName.toLowerCase())) { - throw new IssueError(generateIssue('lazyPartneredSchemasShareTag', { tag: shortName })) + IssueError.generateAndThrow('lazyPartneredSchemasShareTag', { tag: shortName }) } const rootedTagShortName = tag.getNamedAttributeValue('rooted') if (rootedTagShortName) { const parentTag = tag.parent if (parentTag?.name?.toLowerCase() !== rootedTagShortName?.toLowerCase()) { - throw new Error(`Node ${shortName} is improperly rooted.`) + IssueError.generateAndThrow('internalError', { message: `Node ${shortName} is improperly rooted.` }) } }