Skip to content

Commit

Permalink
Finished implementation of the validator for HED
Browse files Browse the repository at this point in the history
  • Loading branch information
VisLab committed Dec 23, 2024
1 parent b315ce5 commit 662e772
Show file tree
Hide file tree
Showing 24 changed files with 67 additions and 11,818 deletions.
6 changes: 3 additions & 3 deletions bids/validator/tsvValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BidsTsvRow } from '../types/tsv'
import { parseHedString } from '../../parser/parser'
import ParsedHedString from '../../parser/parsedHedString'
import { generateIssue } from '../../common/issues/issues'
import { SpecialChecker } from '../../parser/special'
import { ReservedChecker } from '../../parser/reservedChecker'
import { getTagListString } from '../../parser/parseUtils'
import { EventManager } from '../../parser/eventManager'

Expand Down Expand Up @@ -36,7 +36,7 @@ export class BidsHedTsvValidator {
constructor(tsvFile, hedSchemas) {
this.tsvFile = tsvFile
this.hedSchemas = hedSchemas // Will be set when the file is validated
this.special = SpecialChecker.getInstance()
this.special = ReservedChecker.getInstance()
this.issues = []
}

Expand Down Expand Up @@ -172,7 +172,7 @@ export class BidsHedTsvValidator {
const topGroupIssues = []
for (const element of elements) {
const topTags = element.parsedHedString ? element.parsedHedString.topLevelTags : []
const badTags = topTags.filter((tag) => SpecialChecker.hasTopLevelTagGroupAttribute(tag))
const badTags = topTags.filter((tag) => ReservedChecker.hasTopLevelTagGroupAttribute(tag))
if (badTags.length > 0) {
topGroupIssues.push(
BidsHedIssue.fromHedIssue(
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions eventManager/special.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import specialTags from '../data/json/specialTags.json'
import specialTags from '../data/json/reservedTags.json'
import { generateIssue } from '../common/issues/issues'
import { filterTagMapByNames, getTagListString } from './parseUtils'

Expand All @@ -8,7 +8,7 @@ export class SpecialChecker {

constructor() {
if (SpecialChecker.instance) {
throw new Error('Use SpecialChecker.getInstance() to get an instance of this class.')
throw new Error('Use ReservedChecker.getInstance() to get an instance of this class.')
}

this._initializeSpecialTags()
Expand Down
4 changes: 2 additions & 2 deletions parser/parsedHedGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IssueError } from '../common/issues/issues'
import ParsedHedSubstring from './parsedHedSubstring'
import ParsedHedTag from './parsedHedTag'
import ParsedHedColumnSplice from './parsedHedColumnSplice'
import { SpecialChecker } from './special'
import { ReservedChecker } from './reservedChecker'
import {
filterByClass,
categorizeTagsByName,
Expand Down Expand Up @@ -80,7 +80,7 @@ export default class ParsedHedGroup extends ParsedHedSubstring {
}

_initializeGroups() {
const special = SpecialChecker.getInstance()
const special = ReservedChecker.getInstance()
this.specialTags = categorizeTagsByName(this.topTags, special.specialNames)
this.isDefExpandGroup = this.specialTags.has('Def-expand')
this.isDefinitionGroup = this.specialTags.has('Definition')
Expand Down
6 changes: 3 additions & 3 deletions parser/parsedHedTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IssueError } from '../common/issues/issues'
import ParsedHedSubstring from './parsedHedSubstring'
import { SchemaValueTag } from '../schema/entries'
import TagConverter from './tagConverter'
import { SpecialChecker } from './special'
import { ReservedChecker } from './reservedChecker'

const allowedRegEx = /^[^{}\,]*$/

Expand Down Expand Up @@ -124,7 +124,7 @@ export default class ParsedHedTag extends ParsedHedSubstring {
return
}
// Check that there is a value if required
const special = SpecialChecker.getInstance()
const special = ReservedChecker.getInstance()
if (
(schemaTag.hasAttributeName('requireChild') || special.requireValueTags.has(schemaTag.name)) &&
remainder === ''
Expand Down Expand Up @@ -173,7 +173,7 @@ export default class ParsedHedTag extends ParsedHedSubstring {
/**
* Handle special three-level tags
* @param {string} remainder - the remainder of the tag string after schema tag
* @param {SpecialChecker} special - the special checker for checking the special tag properties
* @param {ReservedChecker} special - the special checker for checking the special tag properties
*/
_getSplitValue(remainder, special) {
if (!special.allowTwoLevelValueTags.has(this.schemaTag.name)) {
Expand Down
4 changes: 2 additions & 2 deletions parser/parser.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ParsedHedString from './parsedHedString'
import HedStringSplitter from './splitter'
import { generateIssue } from '../common/issues/issues'
import { SpecialChecker } from './special'
import { ReservedChecker } from './reservedChecker'
import { getTagListString } from './parseUtils'

/**
Expand Down Expand Up @@ -75,7 +75,7 @@ class HedStringParser {
if (simpleDefinitionIssues.length > 0) {
return [null, simpleDefinitionIssues]
}
const checkIssues = SpecialChecker.getInstance().checkHedString(parsedString, fullCheck)
const checkIssues = ReservedChecker.getInstance().checkHedString(parsedString, fullCheck)
if (checkIssues.length > 0) {
return [null, checkIssues]
}
Expand Down
57 changes: 29 additions & 28 deletions parser/special.js → parser/reservedChecker.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@
import specialTags from '../data/json/specialTags.json'
import specialTags from '../data/json/reservedTags.json'
import { generateIssue } from '../common/issues/issues'
import { filterTagMapByNames, getTagListString } from './parseUtils'

export class SpecialChecker {
export class ReservedChecker {
static instance = null
static specialMap = new Map(Object.entries(specialTags))
static reservedMap = new Map(Object.entries(specialTags))

constructor() {
if (SpecialChecker.instance) {
throw new Error('Use SpecialChecker.getInstance() to get an instance of this class.')
if (ReservedChecker.instance) {
throw new Error('Use ReservedChecker.getInstance() to get an instance of this class.')
}

this._initializeSpecialTags()
}

// Static method to control access to the singleton instance
static getInstance() {
if (!SpecialChecker.instance) {
SpecialChecker.instance = new SpecialChecker()
if (!ReservedChecker.instance) {
ReservedChecker.instance = new ReservedChecker()
}
return SpecialChecker.instance
return ReservedChecker.instance
}

_initializeSpecialTags() {
this.specialNames = new Set(SpecialChecker.specialMap.keys())
this.requireValueTags = SpecialChecker._getSpecialTagsByProperty('requireValue')
this.noExtensionTags = SpecialChecker._getSpecialTagsByProperty('noExtension')
this.allowTwoLevelValueTags = SpecialChecker._getSpecialTagsByProperty('allowTwoLevelValue')
this.topGroupTags = SpecialChecker._getSpecialTagsByProperty('topLevelTagGroup')
this.requiresDefTags = SpecialChecker._getSpecialTagsByProperty('requiresDef')
this.groupTags = SpecialChecker._getSpecialTagsByProperty('tagGroup')
this.exclusiveTags = SpecialChecker._getSpecialTagsByProperty('exclusive')
this.temporalTags = SpecialChecker._getSpecialTagsByProperty('isTemporalTag')
this.noSpliceInGroup = SpecialChecker._getSpecialTagsByProperty('noSpliceInGroup')
this.specialNames = new Set(ReservedChecker.reservedMap.keys())
this.requireValueTags = ReservedChecker._getSpecialTagsByProperty('requireValue')
this.noExtensionTags = ReservedChecker._getSpecialTagsByProperty('noExtension')
this.allowTwoLevelValueTags = ReservedChecker._getSpecialTagsByProperty('allowTwoLevelValue')
this.topGroupTags = ReservedChecker._getSpecialTagsByProperty('topLevelTagGroup')
this.requiresDefTags = ReservedChecker._getSpecialTagsByProperty('requiresDef')
this.groupTags = ReservedChecker._getSpecialTagsByProperty('tagGroup')
this.exclusiveTags = ReservedChecker._getSpecialTagsByProperty('exclusive')
this.temporalTags = ReservedChecker._getSpecialTagsByProperty('isTemporalTag')
this.noSpliceInGroup = ReservedChecker._getSpecialTagsByProperty('noSpliceInGroup')
this.hasForbiddenSubgroupTags = new Set(
[...SpecialChecker.specialMap.values()]
[...ReservedChecker.reservedMap.values()]
.filter((value) => value.forbiddenSubgroupTags.length > 0)
.map((value) => value.name),
)
}

static _getSpecialTagsByProperty(property) {
return new Set(
[...SpecialChecker.specialMap.values()].filter((value) => value[property] === true).map((value) => value.name),
[...ReservedChecker.reservedMap.values()].filter((value) => value[property] === true).map((value) => value.name),
)
}

Expand Down Expand Up @@ -123,7 +123,7 @@ export class SpecialChecker {
const topGroupTags = hedString.topLevelGroupTags
hedString.tags.forEach((tag) => {
// Check for top-level violations because tag is deep
if (SpecialChecker.hasTopLevelTagGroupAttribute(tag)) {
if (ReservedChecker.hasTopLevelTagGroupAttribute(tag)) {
//Tag is in a top-level tag group
if (topGroupTags.includes(tag)) {
return
Expand All @@ -139,7 +139,7 @@ export class SpecialChecker {
}

// In final form --- if not in a group (not just a top group) but has the group tag attribute
if (fullCheck && hedString.topLevelTags.includes(tag) && SpecialChecker.hasGroupAttribute(tag)) {
if (fullCheck && hedString.topLevelTags.includes(tag) && ReservedChecker.hasGroupAttribute(tag)) {
issues.push(generateIssue('missingTagGroup', { tag: tag.originalTag, string: hedString.hedString }))
}
})
Expand Down Expand Up @@ -229,7 +229,7 @@ export class SpecialChecker {
* @returns {Issue[]}
*/
_checkGroupRequirements(group, specialTag, fullCheck) {
const specialRequirements = SpecialChecker.specialMap.get(specialTag.schemaTag.name)
const specialRequirements = ReservedChecker.reservedMap.get(specialTag.schemaTag.name)
const issues = this._checkAllowedTags(group, specialTag, specialRequirements.otherAllowedNonDefTags)
if (issues.length > 0) {
return issues
Expand Down Expand Up @@ -360,7 +360,7 @@ export class SpecialChecker {
continue
}
// This tag has
const forbidden = SpecialChecker.specialMap.get(tag.schemaTag.name).forbiddenSubgroupTags
const forbidden = ReservedChecker.reservedMap.get(tag.schemaTag.name).forbiddenSubgroupTags
for (const group of hedString.tagGroups) {
if (group.allTags.some((tag) => forbidden.has(tag.schemaTag.name))) {
return [
Expand Down Expand Up @@ -407,7 +407,7 @@ export class SpecialChecker {
for (const tag of forbiddenTags) {
const otherTags = subGroup.allTags.filter((otherTag) => otherTag !== tag)
const badTags = otherTags.filter((otherTag) =>
SpecialChecker.specialMap.get(tag.schemaTag.name)?.forbiddenSubgroupTags.includes(otherTag.schemaTag.name),
ReservedChecker.reservedMap.get(tag.schemaTag.name)?.forbiddenSubgroupTags.includes(otherTag.schemaTag.name),
)

if (badTags?.length > 0) {
Expand Down Expand Up @@ -435,8 +435,8 @@ export class SpecialChecker {
static hasTopLevelTagGroupAttribute(tag) {
return (
tag.hasAttribute('topLevelTagGroup') ||
(SpecialChecker.specialMap.has(tag.schemaTag.name) &&
SpecialChecker.specialMap.get(tag.schemaTag.name).topLevelTagGroup)
(ReservedChecker.reservedMap.has(tag.schemaTag.name) &&
ReservedChecker.reservedMap.get(tag.schemaTag.name).topLevelTagGroup)
)
}

Expand All @@ -451,7 +451,8 @@ export class SpecialChecker {
static hasGroupAttribute(tag) {
return (
tag.hasAttribute('tagGroup') ||
(SpecialChecker.specialMap.has(tag.schemaTag.name) && SpecialChecker.specialMap.get(tag.schemaTag.name).tagGroup)
(ReservedChecker.reservedMap.has(tag.schemaTag.name) &&
ReservedChecker.reservedMap.get(tag.schemaTag.name).tagGroup)
)
}
}
4 changes: 2 additions & 2 deletions parser/splitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ParsedHedGroup from './parsedHedGroup'
import { recursiveMap } from '../utils/array'
import { HedStringTokenizer, ColumnSpliceSpec, TagSpec } from './tokenizer'
import { generateIssue, IssueError } from '../common/issues/issues'
import { SpecialChecker } from './special'
import { ReservedChecker } from './reservedChecker'

export default class HedStringSplitter {
/**
Expand All @@ -29,7 +29,7 @@ export default class HedStringSplitter {
constructor(hedString, hedSchemas) {
this.hedString = hedString
this.hedSchemas = hedSchemas
this.special = SpecialChecker.getInstance()
this.special = ReservedChecker.getInstance()
this.issues = []
}

Expand Down
4 changes: 2 additions & 2 deletions parser/tagConverter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IssueError } from '../common/issues/issues'
import { getTagSlashIndices } from '../utils/hedStrings'
import { SpecialChecker } from './special'
import { ReservedChecker } from './reservedChecker'

/**
* Converter from a tag specification to a schema-based tag object.
Expand Down Expand Up @@ -62,7 +62,7 @@ export default class TagConverter {
this.tagLevels = this.tagString.split('/')
this.tagSlashes = getTagSlashIndices(this.tagString)
this.remainder = undefined
this.special = SpecialChecker.getInstance()
this.special = ReservedChecker.getInstance()
}

/**
Expand Down
2 changes: 1 addition & 1 deletion schema/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from './entries'
import { IssueError } from '../common/issues/issues'

//const specialTags = require('../data/json/specialTags.json')
//const specialTags = require('../data/json/reservedTags.json')

import classRegex from '../data/json/class_regex.json'

Expand Down
20 changes: 13 additions & 7 deletions spec_tests/jsonTests.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ const runMap = new Map([['TAG_GROUP_ERROR', ['tag-group-error-missing']]])
const runOnly = new Set()

const skippedErrors = {
VERSION_DEPRECATED: 'Not handling in the spec tests',
ELEMENT_DEPRECATED: 'Not handling in this round. This is a warning',
STYLE_WARNING: 'Not handling style warnings at this time',
'invalid-character-name-value-class-deprecated': 'We will let this pass regardless of schema version.',
VERSION_DEPRECATED: 'not handling in the spec tests.',
ELEMENT_DEPRECATED: 'not handling tag deprecated in the spec tests.',
STYLE_WARNING: 'not handling style warnings at this time',
'invalid-character-name-value-class-deprecated': 'not handling deprecated in the spec tests.',
}
const readFileSync = fs.readFileSync
const test_file_name = 'javascriptTests.json'
//const test_file_name = 'temp3.json'

function comboListToStrings(items) {
const comboItems = []
Expand Down Expand Up @@ -238,12 +237,19 @@ describe('HED validation using JSON tests', () => {

afterAll(() => {})

// If debugging a single test
if (!shouldRun(error_code, name, runAll, runMap, skipMap)) {
// eslint-disable-next-line no-console
console.log(`----Skipping JSON Spec tests ${error_code} [${name}]}`)
return
}
if (error_code in skippedErrors || name in skippedErrors || warning) {
test.skip(`Skipping tests ${error_code} skipped because ${skippedErrors['error_code']}`, () => {})
// Run tests except for the ones explicitly skipped or because they are warnings
if (warning) {
test.skip(`Skipping tests ${error_code} [${name}] skipped because warning not error`, () => {})
} else if (error_code in skippedErrors) {
test.skip(`Skipping tests ${error_code} [${name}] skipped because ${skippedErrors[error_code]}`, () => {})
} else if (name in skippedErrors) {
test.skip(`Skipping tests ${error_code} [${name}] skipped because ${skippedErrors[name]}`, () => {})
} else {
test('it should have HED schema defined', () => {
expect(hedSchema).toBeDefined()
Expand Down
Loading

0 comments on commit 662e772

Please sign in to comment.