@@ -4,6 +4,7 @@ var files = require('../utils/files')
44var utils = require ( '../utils' )
55var dateIsValid = require ( 'date-fns/isValid' )
66var parseDate = require ( 'date-fns/parse' )
7+ const nonCustomColumns = require ( '../bids_validator/tsv/non_custom_columns.json' )
78/**
89 * TSV
910 *
@@ -344,7 +345,100 @@ const checkAcqTimeFormat = function(rows, file, issues) {
344345 }
345346}
346347
348+ /**
349+ * @param {Object } file - BIDS file object
350+ * Accepts file object and returns a type based on file path
351+ */
352+ const getTsvType = function ( file ) {
353+ let tsvType = 'misc'
354+ if ( file . relativePath . includes ( 'phenotype/' ) ) {
355+ tsvType = 'phenotype'
356+ } else if ( file . name === 'participants.tsv' ) {
357+ tsvType = 'participants'
358+ } else if (
359+ file . name . endsWith ( '_channels.tsv' ) ||
360+ file . name . endsWith ( '_events.tsv' ) ||
361+ file . name . endsWith ( '_scans.tsv' ) ||
362+ file . name . endsWith ( '_sessions.tsv' )
363+ ) {
364+ const split = file . name . split ( '_' )
365+ tsvType = split [ split . length - 1 ] . replace ( '.tsv' , '' )
366+ }
367+ return tsvType
368+ }
369+
370+ /**
371+ *
372+ * @param {array } headers -Array of column names
373+ * @param {string } type - Type from getTsvType
374+ * Checks TSV column names to determine if they're core or custom
375+ * Returns array of custom column names
376+ */
377+ const getCustomColumns = function ( headers , type ) {
378+ const customCols = [ ]
379+ // Iterate column headers
380+ for ( let col of headers ) {
381+ // If it's a custom column
382+ if ( ! nonCustomColumns [ type ] . includes ( col ) ) {
383+ customCols . push ( col )
384+ }
385+ }
386+ return customCols
387+ }
388+
389+ /**
390+ *
391+ * @param {array } tsvs - Array of objects containing TSV file objects and contents
392+ * @param {Object } jsonContentsDict
393+ */
394+ const validateTsvColumns = function ( tsvs , jsonContentsDict ) {
395+ let tsvIssues = [ ]
396+ tsvs . map ( tsv => {
397+ const customColumns = getCustomColumns (
398+ tsv . contents . split ( '\n' ) [ 0 ] . split ( '\t' ) ,
399+ getTsvType ( tsv . file ) ,
400+ )
401+ if ( customColumns . length > 0 ) {
402+ // Get merged data dictionary for this file
403+ const potentialSidecars = utils . files . potentialLocations (
404+ tsv . file . relativePath . replace ( '.tsv' , '.json' ) ,
405+ )
406+ const mergedDict = utils . files . generateMergedSidecarDict (
407+ potentialSidecars ,
408+ jsonContentsDict ,
409+ )
410+ const keys = Object . keys ( mergedDict )
411+ // Gather undefined columns for the file
412+ let undefinedCols = customColumns . filter ( col => ! keys . includes ( col ) )
413+ // Create an issue for all undefined columns in this file
414+ undefinedCols . length &&
415+ tsvIssues . push (
416+ customColumnIssue (
417+ tsv . file ,
418+ undefinedCols . join ( ', ' ) ,
419+ potentialSidecars ,
420+ ) ,
421+ )
422+ }
423+ } )
424+ // Return array of all instances of undescribed custom columns
425+ return tsvIssues
426+ }
427+
428+ const customColumnIssue = function ( file , col , locations ) {
429+ return new Issue ( {
430+ code : 82 ,
431+ file : file ,
432+ evidence :
433+ 'Columns: ' +
434+ col +
435+ ' not defined, please define in: ' +
436+ locations . toString ( ) . replace ( ',' , ', ' ) ,
437+ } )
438+ }
439+
347440module . exports = {
348441 TSV : TSV ,
349442 checkphenotype : checkphenotype ,
443+ validateTsvColumns : validateTsvColumns ,
350444}
0 commit comments