text: /// <binding AfterBuild='build' />
  const addFormats = require('ajv-formats')
  const ajvFormatsDraft2019 = require('ajv-formats-draft2019')
  const AjvDraft04 = require('ajv-draft-04')
  const AjvDraft06And07 = require('ajv')
  const Ajv2019 = require('ajv/dist/2019')
  const Ajv2020 = require('ajv/dist/2020')
  const tv4 = require('tv4')
  const YAML = require('yaml')
  const pt = require('path')
  const fs = require('fs')
  const temporaryCoverageDir = 'temp'
  const schemaDir = 'schemas/json'
  const testPositiveDir = 'test'
  const testNegativeDir = 'negative_test'
  const urlSchemaStore = 'https://json.schemastore.org/'
  const catalog = require('./api/json/catalog.json')
  const schemaV4JSON = require(pt.resolve('.', schemaDir, 'schema-draft-v4.json'))
  const schemaValidation = require('./schema-validation.json')
  const schemasToBeTested = fs.readdirSync(schemaDir)
  const foldersPositiveTest = fs.readdirSync(testPositiveDir)
  const foldersNegativeTest = fs.readdirSync(testNegativeDir)
  const countSchemasType = [
    { schemaName: '2020-12', schemaStr: 'json-schema.org/draft/2020-12/schema', totalCount: 0, active: true },
    { schemaName: '2019-09', schemaStr: 'json-schema.org/draft/2019-09/schema', totalCount: 0, active: true },
    { schemaName: 'draft-07', schemaStr: 'json-schema.org/draft-07/schema', totalCount: 0, active: true },
    { schemaName: 'draft-06', schemaStr: 'json-schema.org/draft-06/schema', totalCount: 0, active: true },
    { schemaName: 'draft-04', schemaStr: 'json-schema.org/draft-04/schema', totalCount: 0, active: true },
    { schemaName: 'draft-03', schemaStr: 'json-schema.org/draft-03/schema', totalCount: 0, active: false },
    { schemaName: 'draft without version', schemaStr: 'json-schema.org/schema', totalCount: 0, active: false }
  ]
  module.exports = function (grunt) {
    'use strict'
  
    function skipThisFileName (name) {
      // This macOS file must always be ignored.
      return name === '.DS_Store'
    }
  
    function getUrlFromCatalog (catalogUrl) {
      for (const schema of catalog.schemas) {
        catalogUrl(schema.url)
        const versions = schema.versions
        if (versions) {
          Object.values(versions).forEach(url => catalogUrl(url))
        }
      }
    }
  
    /**
     * @summary Create an exception with error text
     * Make sure that the user see this error message.
     * And not only the error message generated by npm after this message.
     * @param {string[]} errorText
     */
    function throwWithErrorText (errorText) {
      grunt.log.writeln()
      grunt.log.writeln()
      grunt.log.writeln('################ Error message')
      for (const text of errorText) {
        grunt.log.error(text)
      }
      grunt.log.writeln('##############################')
      throw new Error('See error message above this line.')
    }
  
    async function remoteSchemaFile (schemaOnlyScan, showLog = true) {
      const got = require('got')
      const schemas = catalog.schemas
  
      for (const { url } of schemas) {
        if (url.startsWith(urlSchemaStore)) {
          // Skip local schema
          continue
        }
        try {
          const response = await got(url)
          if (response.statusCode === 200) {
            const parsed = new URL(url)
            const callbackParameter = {
              jsonName: pt.basename(parsed.pathname),
              rawFile: response.rawBody,
              urlOrFilePath: url,
              schemaScan: true
            }
            schemaOnlyScan(callbackParameter)
            if (showLog) {
              grunt.log.ok(url)
            }
          } else {
            if (showLog) {
              grunt.log.error(url, response.statusCode)
            }
          }
        } catch (error) {
          if (showLog) {
            grunt.log.writeln('')
            grunt.log.error(url, error.name, error.message)
            grunt.log.writeln('')
          }
        }
      }
    }
  
    function localSchemaFileAndTestFile (
      {
        schemaOnlyScan = undefined,
        schemaOnlyScanDone = undefined,
        schemaForTestScan = undefined,
        schemaForTestScanDone = undefined,
        positiveTestScan = undefined,
        positiveTestScanDone = undefined,
        negativeTestScan = undefined,
        negativeTestScanDone = undefined
      },
      {
        fullScanAllFiles = false,
        calledByTV4Validator = false,
        skipReadFile = true,
        processOnlyThisOneSchemaFile = undefined
      } = {}) {
      /**
       * @summary Check if the present json schema file must be tested or not
       * @param {string} jsonFilename
       * @returns {boolean}
       */
      const canThisTestBeRun = (jsonFilename) => {
        if (schemaValidation.skiptest.includes(jsonFilename)) {
          return false // This test can be never process
        }
        if (fullScanAllFiles) {
          return true // All tests are always performed.
        } else {
          // Schema must be run for tv4 or AJV validator
          // calledByTV4Validator is only set true when it is called by tv4 validator
          // If schema is present in "tv4test" list then it can only be run if calledByTV4Validator = true
          // If schema is NOT present in "tv4test" list then it can only be run if calledByTV4Validator = false
          return schemaValidation.tv4test.includes(jsonFilename) ? calledByTV4Validator : !calledByTV4Validator
        }
      }
  
      /**
       * @summary Get all the schema files via callback
       * @param callback The callback function(callbackParameter)
       * @param {boolean} onlySchemaScan True = a scan without test files.
       */
      const scanAllSchemaFiles = (callback, onlySchemaScan) => {
        if (!callback) {
          return
        }
        // Process all the schema files one by one via callback.
        schemasToBeTested.forEach((schemaFileName) => {
          if (processOnlyThisOneSchemaFile) {
            if (schemaFileName !== processOnlyThisOneSchemaFile) return
          }
          const schemaFullPathName = pt.join(schemaDir, schemaFileName)
  
          // Some schema files must be ignored.
          if (canThisTestBeRun(schemaFileName) &&
              !skipThisFileName(schemaFileName)) {
            const callbackParameter = {
              // Return the real Raw file for BOM file test rejection
              rawFile: skipReadFile ? undefined : fs.readFileSync(schemaFullPathName),
              jsonName: pt.basename(schemaFullPathName),
              urlOrFilePath: schemaFullPathName,
              schemaScan: onlySchemaScan
            }
            callback(callbackParameter)
          }
        })
      }
  
      // Scan one test folder for all the files inside it
      const scanOneTestFolder = (schemaName, testDir, testPassScan, testPassScanDone) => {
        const loadTestFile = (testFileNameWithPath) => {
          // Test files have extension '.json' or else it must be a YAML file
          if (testFileNameWithPath.endsWith('.json')) {
            return grunt.file.read(testFileNameWithPath)
          } else { // YAML file
            try {
              return JSON.stringify(YAML.parse(fs.readFileSync(testFileNameWithPath, 'utf8')))
            } catch (e) {
              throwWithErrorText([`Can't read/decode yaml file: ${testFileNameWithPath}`, e])
            }
          }
        }
  
        if (!testPassScan) {
          return
        }
        // remove filename '.json' extension and to create the folder name
        const folderNameAndPath = pt.join(testDir, pt.basename(schemaName, '.json'))
        // if test folder doesn't exist then exit. Some schemas do not have a test folder.
        if (!fs.existsSync(folderNameAndPath)) {
          return
        }
  
        // Read all files name inside one test folder
        const filesInsideOneTestFolder = fs.readdirSync(folderNameAndPath).map(
          // Must create a list with full path name
          (fileName) => pt.join(folderNameAndPath, fileName)
        )
  
        if (!filesInsideOneTestFolder.length) {
          throwWithErrorText([`Found folder with no test files: ${folderNameAndPath}`])
        }
  
        // Test file may have BOM. This must be removed.
        grunt.file.preserveBOM = false // Strip BOM from file
        filesInsideOneTestFolder.forEach(function (testFileFullPathName) {
          // forbidden to add extra folder inside the specific test folder
          if (!fs.lstatSync(testFileFullPathName).isFile()) {
            throwWithErrorText([`Found non test file inside test folder: ${testFileFullPathName}`])
          }
          if (!skipThisFileName(pt.basename(testFileFullPathName))) {
            const callbackParameter = {
              rawFile: skipReadFile ? undefined : loadTestFile(testFileFullPathName),
              jsonName: pt.basename(testFileFullPathName),
              urlOrFilePath: testFileFullPathName,
              // This is a test folder scan process, not schema scan process
              schemaScan: false
            }
            testPassScan(callbackParameter)
          }
        })
        testPassScanDone?.()
      }
  
      // Callback only for schema file scan. No test files are process here.
      scanAllSchemaFiles(schemaOnlyScan, true)
      schemaOnlyScanDone?.()
  
      // process one by one all schema + positive test folders + negative test folders
      scanAllSchemaFiles((callbackParameterFromSchema) => {
        // process one schema
        schemaForTestScan?.(callbackParameterFromSchema)
        // process positive and negative test folder belonging to the one schema
        const schemaName = callbackParameterFromSchema.jsonName
        scanOneTestFolder(schemaName, testPositiveDir, positiveTestScan, positiveTestScanDone)
        scanOneTestFolder(schemaName, testNegativeDir, negativeTestScan, negativeTestScanDone)
      },
      false
      )
      schemaForTestScanDone?.()
    }
  
    function testSchemaFileForBOM (callbackParameter) {
      // JSON schema file must not have any BOM type
      const buffer = callbackParameter.rawFile
      const bomTypes = [
        { name: 'UTF-8', signature: [0xEF, 0xBB, 0xBF] },
        { name: 'UTF-16 (BE)', signature: [0xFE, 0xFF] },
        { name: 'UTF-16 (LE)', signature: [0xFF, 0xFE] },
        { name: 'UTF-32 (BE)', signature: [0x00, 0x00, 0xFF, 0xFE] },
        { name: 'UTF-32 (LE)', signature: [0xFF, 0xFE, 0x00, 0x00] }
      ]
  
      for (const bom of bomTypes) {
        if (buffer.length >= bom.signature.length) {
          const bomFound = bom.signature.every((value, index) => buffer[index] === value)
          if (bomFound) {
            throwWithErrorText([`Schema file must not have ${bom.name} BOM: ${callbackParameter.urlOrFilePath}`])
          }
        }
      }
    }
  
    function tv4Validator () {
      // tv4 validator can only process draft-04 schema
      // All unknown keyword used in draft-06 and newer are just ignored.
      // This is the correct implementation of the json schema specification.
      const schemaVersion = showSchemaVersions()
      const textValidate = 'validate              | '
      const textPassSchema = 'pass schema          | '
      const textPositivePassTest = 'pass positive test   | '
      const textPositiveFailedTest = 'failed positive test | '
  
      let schemaToBeValidated
      let countSchema = 0
      const processSchemaFile = (callbackParameter) => {
        // Start validate the JSON schema
        let validated
        let versionObj
        let schemaVersionStr = 'unknown'
        try {
          // select the correct AJV object for this schema
          schemaToBeValidated = JSON.parse(callbackParameter.rawFile)
          versionObj = schemaVersion.getObj(schemaToBeValidated)
  
          // What schema draft version is it?
          schemaVersionStr = versionObj ? versionObj.schemaName : 'unknown'
  
          // validate the schema with draft-04. This is the only draft version it understands.
          validated = tv4.validate(schemaToBeValidated, schemaV4JSON)
        } catch (e) {
          throwWithErrorText([`${textValidate}${callbackParameter.urlOrFilePath} (${schemaVersionStr})`, e])
        }
        if (!validated) {
          throwWithErrorText([
            `${textValidate}${callbackParameter.urlOrFilePath} (${schemaVersionStr}`,
            `(Schema file) keywordLocation: ${tv4.error.schemaPath}`,
            `(Test file) instanceLocation:  ${tv4.error.dataPath}`,
            `(Message)  ${tv4.error.message}`,
            'Error in validation'
          ])
        }
        countSchema++
        grunt.log.writeln()
        grunt.log.ok(`${textPassSchema}${callbackParameter.urlOrFilePath} (${schemaVersionStr})`)
      }
  
      const processSchemaFileDone = () => {
        grunt.log.writeln()
        grunt.log.writeln(`Total schemas validated with tv4: ${countSchema}`)
        countSchema = 0
      }
  
      const processPositiveTestFile = (callbackParameter) => {
        const testFile = JSON.parse(callbackParameter.rawFile)
        const validated = tv4.validate(testFile, schemaToBeValidated)
        if (tv4.missing.length > 0) {
          throwWithErrorText([`${textPositiveFailedTest}${callbackParameter.urlOrFilePath}`,
            `Missing URL: ${tv4.missing[0]}`,
            'Must add URL and schema file in schema-validation.json (tv4ExternalRef list)'
          ])
        }
        if (validated) {
          grunt.log.ok(`${textPositivePassTest}${callbackParameter.urlOrFilePath}`)
        } else {
          throwWithErrorText([
            `${textPositiveFailedTest}${callbackParameter.urlOrFilePath}`,
            `(Schema file) keywordLocation: ${tv4.error.schemaPath}`,
            `(Test file) instanceLocation:  ${tv4.error.dataPath}`,
            `(Message)  ${tv4.error.message}`,
            'Error in positive test.'
          ])
        }
      }
  
      const loadExternalSchema = () => {
        for (const property in schemaValidation.tv4ExternalRef) {
          try {
            const schema = require(pt.resolve('.', schemaDir, schemaValidation.tv4ExternalRef[property]))
            tv4.addSchema(property, schema)
          } catch (e) {
            throwWithErrorText([`Error in processing tv4ExternalRef ${property} : ${schemaValidation.tv4ExternalRef[property]}`, e])
          }
        }
      }
  
      loadExternalSchema()
      return {
        testSchemaFile: processSchemaFile,
        testSchemaFileDone: processSchemaFileDone,
        positiveTestFile: processPositiveTestFile
      }
    }
  
    /**
     * There are multiple AJV version for each $schema version.
     * return the correct AJV instance
     * @param {string} schemaName
     * @param {string[]} unknownFormatsList
     * @param {boolean} fullStrictMode
     * @param {boolean} standAloneCode
     * @param {string[]} standAloneCodeWithMultipleSchema
     * @returns {Object}
     */
    function factoryAJV ({ schemaName, unknownFormatsList = [], fullStrictMode = true, standAloneCode = false, standAloneCodeWithMultipleSchema = [] } = {}) {
      // some AJV default setting are [true, false or log]
      // Some options are default: 'log'
      // 'log' will generate a lot of noise in the build log. So make it true or false.
      // Hiding the issue log also does not solve anything.
      // These option items that are not strict must be reduces in the future.
      const ajvOptionsNotStrictMode = {
        strictTypes: false, // recommended : true
        strictTuples: false, // recommended : true
        allowMatchingProperties: true // recommended : false
      }
      const ajvOptionsStrictMode = {
        strict: true
      }
      const ajvOptions = fullStrictMode ? ajvOptionsStrictMode : ajvOptionsNotStrictMode
  
      // Stand-alone code need some special options parameters
      if (standAloneCode) {
        ajvOptions.code = { source: true }
        if (standAloneCodeWithMultipleSchema.length) {
          ajvOptions.schemas = standAloneCodeWithMultipleSchema
        }
      }
      let ajvSelected
      // There are multiple AJV version for each $schema version.
      // Create the correct one.
      switch (schemaName) {
        case 'draft-04':
          ajvSelected = new AjvDraft04(ajvOptions)
          break
        case 'draft-06':
        case 'draft-07':
          ajvSelected = new AjvDraft06And07(ajvOptions)
          if (schemaName === 'draft-06') {
            ajvSelected.addMetaSchema(require('ajv/dist/refs/json-schema-draft-06.json'))
          } else {
            // 'draft-07' have additional format
            ajvFormatsDraft2019(ajvSelected)
          }
          break
        case '2019-09':
          ajvSelected = new Ajv2019(ajvOptions)
          ajvFormatsDraft2019(ajvSelected)
          break
        case '2020-12':
          ajvSelected = new Ajv2020(ajvOptions)
          ajvFormatsDraft2019(ajvSelected)
          break
        default:
          ajvSelected = new AjvDraft04(ajvOptions)
      }
  
      // addFormats() and addFormat() to the latest AJV version
      addFormats(ajvSelected)
      unknownFormatsList.forEach((x) => {
        ajvSelected.addFormat(x, true)
      })
      return ajvSelected
    }
  
    /**
     * Get the option items for this specific jsonName
     * @param {string} jsonName
     * @returns {
     * {unknownFormatsList: string[],
     * externalSchemaWithPathList: string[],
     * unknownKeywordsList: string[]}
     * }
     */
    function getOption (jsonName) {
      const options = schemaValidation.options.find(
        item => jsonName in item
      )?.[jsonName]
  
      // collect the unknownFormat list
      const unknownFormatsList = options?.unknownFormat ?? []
  
      // collect the unknownKeywords list
      const unknownKeywordsList = options?.unknownKeywords ?? []
  
      // collect the externalSchema list
      const externalSchemaList = options?.externalSchema ?? []
      const externalSchemaWithPathList = externalSchemaList?.map((schemaFileName) => {
        return pt.resolve('.', schemaDir, schemaFileName)
      })
  
      // return all the collected values
      return {
        unknownFormatsList,
        unknownKeywordsList,
        externalSchemaWithPathList
      }
    }
  
    function ajv () {
      const schemaVersion = showSchemaVersions()
      const textCompile = 'compile              | '
      const textPassSchema = 'pass schema          | '
      const textPositivePassTest = 'pass positive test   | '
      const textPositiveFailedTest = 'failed positive test | '
      const textNegativePassTest = 'pass negative test   | '
      const textNegativeFailedTest = 'failed negative test | '
  
      let validate
      let countSchema = 0
  
      const processSchemaFile = (callbackParameter) => {
        let ajvSelected
  
        // Get possible options define in schema-validation.json
        const {
          unknownFormatsList,
          unknownKeywordsList,
          externalSchemaWithPathList
        } = getOption(callbackParameter.jsonName)
  
        // Start validate the JSON schema
        let schemaJson
        let versionObj
        let schemaVersionStr = 'unknown'
        const fullStrictMode = schemaValidation.ajvFullStrictMode.includes(callbackParameter.jsonName)
        const fullStrictModeStr = fullStrictMode ? '(FullStrictMode)' : ''
        try {
          // select the correct AJV object for this schema
          schemaJson = JSON.parse(callbackParameter.rawFile)
          versionObj = schemaVersion.getObj(schemaJson)
  
          // Get the correct AJV version
          ajvSelected = factoryAJV({
            schemaName: versionObj?.schemaName,
            unknownFormatsList: unknownFormatsList,
            fullStrictMode: fullStrictMode
          })
  
          // AJV must ignore these keywords
          unknownKeywordsList?.forEach((x) => {
            ajvSelected.addKeyword(x)
          })
  
          // Add external schema to AJV
          externalSchemaWithPathList.forEach((x) => {
            ajvSelected.addSchema(require(x.toString()))
          })
  
          // What schema draft version is it?
          schemaVersionStr = versionObj ? versionObj.schemaName : 'unknown'
  
          // compile the schema
          validate = ajvSelected.compile(schemaJson)
        } catch (e) {
          throwWithErrorText([`${textCompile}${callbackParameter.urlOrFilePath} (${schemaVersionStr})${fullStrictModeStr}`, e])
        }
        countSchema++
        grunt.log.writeln()
        grunt.log.ok(`${textPassSchema}${callbackParameter.urlOrFilePath} (${schemaVersionStr})${fullStrictModeStr}`)
      }
  
      const processTestFile = (callbackParameter, success, failure) => {
        let json
        try {
          json = JSON.parse(callbackParameter.rawFile)
        } catch (e) {
          throwWithErrorText([`Error in parse test: ${callbackParameter.urlOrFilePath}`, e])
        }
        validate(json) ? success() : failure()
      }
  
      const processPositiveTestFile = (callbackParameter) => {
        processTestFile(callbackParameter,
          () => {
            grunt.log.ok(`${textPositivePassTest}${callbackParameter.urlOrFilePath}`)
          },
          () => {
            throwWithErrorText([
              `${textPositiveFailedTest}${callbackParameter.urlOrFilePath}`,
              `(Schema file) keywordLocation: ${validate.errors[0].schemaPath}`,
              `(Test file) instanceLocation:  ${validate.errors[0].instancePath}`,
              `(Message)  ${validate.errors[0].message}`,
              'Error in positive test.'
            ])
          }
        )
      }
  
      const processNegativeTestFile = (callbackParameter) => {
        processTestFile(callbackParameter,
          () => {
            throwWithErrorText([
              `${textNegativeFailedTest}${callbackParameter.urlOrFilePath}`,
              'Negative test must always fail.'
            ])
          },
          () => {
            // must show log as single line
            // const path = validate.errors[0].instancePath
            let text = ''
            text = text.concat(`${textNegativePassTest}${callbackParameter.urlOrFilePath}`)
            text = text.concat(` (Schema: ${validate.errors[0].schemaPath})`)
            text = text.concat(` (Test: ${validate.errors[0].instancePath})`)
            text = text.concat(` (Message): ${validate.errors[0].message})`)
            grunt.log.ok(text)
          }
        )
      }
  
      const processSchemaFileDone = () => {
        grunt.log.writeln()
        grunt.log.writeln(`Total schemas validated with AJV: ${countSchema}`)
        countSchema = 0
      }
  
      return {
        testSchemaFile: processSchemaFile,
        testSchemaFileDone: processSchemaFileDone,
        positiveTestFile: processPositiveTestFile,
        negativeTestFile: processNegativeTestFile
      }
    }
  
    grunt.registerTask('local_tv4_only_for_non_compliance_schema', 'Dynamically load local schema file for validation with /test/', function () {
      const x = tv4Validator()
      // tv4 is an outdated/unreliable validator. Do not add a negative test scan here.
      localSchemaFileAndTestFile({
        schemaForTestScan: x.testSchemaFile,
        positiveTestScan: x.positiveTestFile,
        schemaForTestScanDone: x.testSchemaFileDone
      }, { calledByTV4Validator: true, skipReadFile: false })
    })
  
    grunt.registerTask('local_ajv_test', 'Dynamically load local schema file for validation with /test/', function () {
      const x = ajv()
      localSchemaFileAndTestFile({
        schemaForTestScan: x.testSchemaFile,
        positiveTestScan: x.positiveTestFile,
        negativeTestScan: x.negativeTestFile,
        schemaForTestScanDone: x.testSchemaFileDone
      }, { skipReadFile: false })
      grunt.log.ok('local AJV schema passed')
    })
  
    grunt.registerTask('remote_ajv_test', 'Dynamically load external schema file for validation', async function () {
      const done = this.async()
      const x = ajv()
      let countScan = 0
      await remoteSchemaFile(testSchemaFile => {
        x.testSchemaFile(testSchemaFile)
        countScan++
      })
      grunt.log.writeln()
      grunt.log.writeln(`Total schemas validated with AJV: ${countScan}`)
      done()
    })
  
    grunt.registerTask('local_bom', 'Dynamically load local schema file for BOM validation', function () {
      let countScan = 0
      const x = (data) => {
        countScan++
        testSchemaFileForBOM(data)
      }
      localSchemaFileAndTestFile({ schemaOnlyScan: x }, { fullScanAllFiles: true, skipReadFile: false })
      grunt.log.ok(`no BOM file found in all schema files. Total files scan: ${countScan}`)
    })
  
    grunt.registerTask('remote_bom', 'Dynamically load remote schema file for BOM validation', async function () {
      const done = this.async()
      await remoteSchemaFile(testSchemaFileForBOM, false)
      done()
    })
  
    grunt.registerTask('local_catalog', 'Catalog validation', function () {
      const catalogSchema = require(pt.resolve('.', schemaDir, 'schema-catalog.json'))
      const ajvInstance = factoryAJV({ schemaName: 'draft-04' })
      if (ajvInstance.validate(catalogSchema, catalog)) {
        grunt.log.ok('catalog.json OK')
      } else {
        throwWithErrorText([
          `(Schema file) keywordLocation: ${ajvInstance.errors[0].schemaPath}`,
          `(Catalog file) instanceLocation: ${ajvInstance.errors[0].instancePath}`,
          `(message) instanceLocation: ${ajvInstance.errors[0].message}`,
          '"Catalog ERROR"'
        ])
      }
    })
  
    grunt.registerTask('local_find-duplicated-property-keys', 'Dynamically load local test file for validation', function () {
      const findDuplicatedPropertyKeys = require('find-duplicated-property-keys')
      let countScan = 0
      const findDuplicatedProperty = (callbackParameter) => {
        countScan++
        let result
        try {
          result = findDuplicatedPropertyKeys(callbackParameter.rawFile)
        } catch (e) {
          throwWithErrorText([`Test file: ${callbackParameter.urlOrFilePath}`, e])
        }
        if (result.length > 0) {
          const errorText = []
          errorText.push(`Duplicated key found in: ${callbackParameter.urlOrFilePath}`)
          for (const issue of result) {
            errorText.push(`${issue.key} <= This duplicate key is found. occurrence :${issue.occurrence.toString()}`)
          }
          errorText.push('Error in test: find-duplicated-property-keys')
          throwWithErrorText(errorText)
        }
      }
      localSchemaFileAndTestFile({ positiveTestScan: findDuplicatedProperty, negativeTestScan: findDuplicatedProperty }, { skipReadFile: false })
      grunt.log.ok(`No duplicated property key found in test files. Total files scan: ${countScan}`)
    })
  
    grunt.registerTask('local_url-present-in-catalog', 'local url must reference to a file', function () {
      const urlRecommendation = 'https://json.schemastore.org/<schemaName>.json'
      let countScan = 0
  
      getUrlFromCatalog(catalogUrl => {
        // URL that does not have "schemastore.org" anywhere is an external schema.
        // Then there is no need for further URL syntax check.
        if (!catalogUrl.includes('schemastore.org')) {
          return
        }
        countScan++
        // Check if local URL is a valid format with subdomain format.
        if (!catalogUrl.startsWith(urlSchemaStore)) {
          throwWithErrorText([`Wrong: ${catalogUrl} Must be in this format: ${urlRecommendation}`])
        }
        // Check if local URL have .json extension
        const filenameMustBeAtThisUrlDepthPosition = 3
        const filename = catalogUrl.split('/')[filenameMustBeAtThisUrlDepthPosition]
        if (!filename?.endsWith('.json')) {
          throwWithErrorText([
            `Wrong: ${catalogUrl} Missing ".json" extension.`,
            `Must be in this format: ${urlRecommendation}`])
        }
        // Check if schema file exist or not.
        if (fs.existsSync(pt.resolve('.', schemaDir, filename)) === false) {
          throwWithErrorText([
            `The catalog have this URL: ${catalogUrl}`,
            `But there is no schema file present: ${filename}`])
        }
      })
      grunt.log.ok(`All local url tested OK. Total: ${countScan}`)
    })
  
    grunt.registerTask('local_schema-present-in-catalog-list', 'local schema must have a url reference in catalog list', function () {
      let countScan = 0
      const allCatalogLocalJsonFiles = []
  
      // Read all the JSON file name from catalog and add it to allCatalogLocalJsonFiles[]
      getUrlFromCatalog(catalogUrl => {
        // No need to validate the local URL correctness. It is al ready done in "local_url-present-in-catalog"
        // Only scan for local schema.
        if (catalogUrl.startsWith(urlSchemaStore)) {
          const filename = catalogUrl.split('/').pop()
          allCatalogLocalJsonFiles.push(filename)
        }
      })
  
      // Check if allCatalogLocalJsonFiles[] have the actual schema filename.
      const schemaFileCompare = (x) => {
        // skip testing if present in "missingcatalogurl"
        if (!schemaValidation.missingcatalogurl.includes(x.jsonName)) {
          countScan++
          const found = allCatalogLocalJsonFiles.includes(x.jsonName)
          if (!found) {
            throwWithErrorText([
              'Schema file name must be present in the catalog URL.',
              `${x.jsonName} must be present in src/api/json/catalog.json`
            ])
          }
        }
      }
      // Get all the json file for AJV and tv4
      localSchemaFileAndTestFile({ schemaOnlyScan: schemaFileCompare }, { fullScanAllFiles: true })
      grunt.log.ok(`All local schema files have URL link in catalog. Total: ${countScan}`)
    })
  
    grunt.registerTask('local_catalog-fileMatch-conflict', 'note: app.json and *app.json conflicting will not be detected', function () {
      const fileMatchConflict = schemaValidation.fileMatchConflict
      let fileMatchCollection = []
      // Collect all the "fileMatch" and put it in fileMatchCollection[]
      for (const schema of catalog.schemas) {
        const fileMatchArray = schema.fileMatch
        if (fileMatchArray) {
          // Check if this is already present in the "fileMatchConflict" list. If so then remove it from filtered[]
          const filtered = fileMatchArray.filter(fileMatch => {
            return !fileMatchConflict.includes(fileMatch)
          })
          // Check if fileMatch is already present in the fileMatchCollection[]
          filtered.forEach(fileMatch => {
            if (fileMatchCollection.includes(fileMatch)) {
              throwWithErrorText([`Duplicate fileMatch found => ${fileMatch}`])
            }
          })
          fileMatchCollection = fileMatchCollection.concat(filtered)
        }
      }
      grunt.log.ok('No new fileMatch conflict detected.')
    })
  
    grunt.registerTask('local_check_filename_extension', 'Dynamically check local schema/test file for filename extension', function () {
      const schemaFileExtension = ['.json']
      const testFileExtension = ['.json', '.yml', '.yaml']
      let countScan = 0
      const x = (data, fileExtensionList) => {
        countScan++
        const found = fileExtensionList.find(x => data.jsonName.endsWith(x))
        if (!found) {
          throwWithErrorText([`Filename must have ${fileExtensionList} extension => ${data.urlOrFilePath}`])
        }
      }
      localSchemaFileAndTestFile(
        {
          schemaForTestScan: (data) => x(data, schemaFileExtension),
          positiveTestScan: (data) => x(data, testFileExtension),
          negativeTestScan: (data) => x(data, testFileExtension)
        }, {
          fullScanAllFiles: true
        })
      grunt.log.ok(`All schema and test filename have the correct file extension. Total files scan: ${countScan}`)
    })
  
    grunt.registerTask('local_search_for_schema_without_positive_test_files', 'Dynamically check local schema if positive test files are present', function () {
      let countMissingTest = 0
      // Check if each schemasToBeTested[] items is present in foldersPositiveTest[]
      schemasToBeTested.forEach(schemaFileName => {
        if (!foldersPositiveTest.includes(schemaFileName.replace('.json', ''))) {
          countMissingTest++
          grunt.log.ok(`(No positive test file present): ${schemaFileName}`)
        }
      })
      if (countMissingTest > 0) {
        const percent = (countMissingTest / schemasToBeTested.length) * 100
        grunt.log.writeln()
        grunt.log.writeln(`${Math.round(percent)}% of schemas do not have tests.`)
        grunt.log.ok(`Schemas that have no positive test files. Total files: ${countMissingTest}`)
      } else {
        grunt.log.ok('All schemas have positive test')
      }
    })
  
    grunt.registerTask('local_validate_directory_structure', 'Dynamically check if schema and test directory structure are valid', function () {
      schemasToBeTested.forEach((name) => {
        if (!skipThisFileName(name) && !fs.lstatSync(pt.join(schemaDir, name)).isFile()) {
          throwWithErrorText([`There can only be files in directory : ${schemaDir} => ${name}`])
        }
      })
  
      foldersPositiveTest.forEach((name) => {
        if (!skipThisFileName(name) && !fs.lstatSync(pt.join(testPositiveDir, name)).isDirectory()) {
          throwWithErrorText([`There can only be directory's in :${testPositiveDir} => ${name}`])
        }
      })
  
      foldersNegativeTest.forEach((name) => {
        if (!skipThisFileName(name) && !fs.lstatSync(pt.join(testNegativeDir, name)).isDirectory()) {
          throwWithErrorText([`There can only be directory's in :${testNegativeDir} => ${name}`])
        }
      })
      grunt.log.ok('OK')
    })
  
    grunt.registerTask('local_test_downgrade_schema_version', 'Dynamically check local schema version is not to high', function () {
      const countSchemas = countSchemasType
      let countScan = 0
      let option
  
      const validateViaAjv = (schemaJson, schemaName, option) => {
        try {
          const ajvSelected = factoryAJV({
            schemaName: schemaName,
            unknownFormatsList: option.unknownFormatsList,
            fullStrictMode: false
          })
  
          // AJV must ignore these keywords
          option.unknownKeywordsList?.forEach((x) => {
            ajvSelected.addKeyword(x)
          })
  
          // Add external schema to AJV
          option.externalSchemaWithPathList.forEach((x) => {
            ajvSelected.addSchema(require(x.toString()))
          })
  
          ajvSelected.compile(schemaJson)
          return true
        } catch (e) {
          return false
        }
      }
  
      // There are no positive or negative test processes here.
      // Only the schema files are tested.
      const testLowerSchemaVersion = (callbackParameter) => {
        countScan++
        let versionIndexOriginal = 0
        const schemaJson = JSON.parse(callbackParameter.rawFile)
  
        if (!('$schema' in schemaJson)) {
          // There is no $schema present in the file.
          return
        }
  
        option = getOption(callbackParameter.jsonName)
  
        // get the present schema_version
        const schemaVersion = schemaJson.$schema
        for (const [index, value] of countSchemas.entries()) {
          if (schemaVersion.includes(value.schemaStr)) {
            versionIndexOriginal = index
            break
          }
        }
  
        // start testing each schema version in a while loop.
        let result = false
        let recommendedIndex = versionIndexOriginal
        let versionIndexToBeTested = versionIndexOriginal
        do {
          // keep trying to use the next lower schema version from the countSchemas[]
          versionIndexToBeTested++
          const schemaVersionToBeTested = countSchemas[versionIndexToBeTested]
          if (!schemaVersionToBeTested?.active) {
            // Can not use this schema version. And there are no more 'active' list item left.
            break
          }
  
          if (schemaVersionToBeTested.schemaName === 'draft-06') {
            // Not interested in downgrading to "draft-06". Skip this one.
            result = true
            continue
          }
  
          // update the schema with a new alternative $schema version
          schemaJson.$schema = `http://${schemaVersionToBeTested.schemaStr}`
          // Test this new updated schema with AJV
          result = validateViaAjv(schemaJson, schemaVersionToBeTested.schemaName, option)
  
          if (result) {
            // It passes the test. So this is the new recommended index
            recommendedIndex = versionIndexToBeTested
          }
          // keep in the loop till it fail the validation process.
        } while (result)
  
        if (recommendedIndex !== versionIndexOriginal) {
          // found a different schema version that also work.
          const original = countSchemas[versionIndexOriginal].schemaName
          const recommended = countSchemas[recommendedIndex].schemaName
          grunt.log.ok(`${callbackParameter.jsonName} (${original}) is also valid with (${recommended})`)
        }
      }
  
      grunt.log.writeln()
      grunt.log.ok('Check if a lower $schema version will also pass the schema validation test')
      localSchemaFileAndTestFile({ schemaOnlyScan: testLowerSchemaVersion }, { skipReadFile: false })
      grunt.log.writeln()
      grunt.log.ok(`Total files scan: ${countScan}`)
    })
  
    function showSchemaVersions () {
      const countSchemas = countSchemasType
      let countSchemaVersionUnknown = 0
  
      const getObj_ = (schemaJson) => {
        const schemaVersion = schemaJson?.$schema
        return countSchemas.find(obj => schemaVersion?.includes(obj.schemaStr))
      }
  
      return {
        getObj: getObj_,
        process_data: (callbackParameter) => {
          let obj
          try {
            obj = getObj_(JSON.parse(callbackParameter.rawFile))
          } catch (e) {
            // suppress possible JSON.parse exception. It will be processed as obj = undefined
          }
          if (obj) {
            obj.totalCount++
          } else {
            countSchemaVersionUnknown++
            grunt.log.error(`$schema is unknown in the file: ${callbackParameter.urlOrFilePath}`)
          }
        },
        process_data_done: () => {
          // Show the all the schema version count.
          for (const obj of countSchemas) {
            grunt.log.ok(`Schemas using (${obj.schemaName}) Total files: ${obj.totalCount}`)
          }
          grunt.log.ok(`$schema unknown. Total files: ${countSchemaVersionUnknown}`)
        }
      }
    }
  
    grunt.registerTask('local_count_schema_versions', 'Dynamically check local schema for schema version count', function () {
      const x = showSchemaVersions()
      localSchemaFileAndTestFile({
        schemaOnlyScan: x.process_data,
        schemaOnlyScanDone: x.process_data_done
      },
      {
        fullScanAllFiles: true,
        skipReadFile: false
      })
    })
  
    grunt.registerTask('remote_count_schema_versions', 'Dynamically load remote schema file for schema version count', async function () {
      const done = this.async()
      const x = showSchemaVersions()
      await remoteSchemaFile((callbackParameter) => { x.process_data(callbackParameter) }, false)
      x.process_data_done()
      done()
    })
  
    grunt.registerTask('local_check_for_schema_version_present', 'Dynamically load schema file for $schema present check', function () {
      let countScan = 0
      localSchemaFileAndTestFile({
        schemaOnlyScan: function (callbackParameter) {
          countScan++
          let schemaJson
          try {
            schemaJson = JSON.parse(callbackParameter.rawFile)
          } catch (err) {
            throwWithErrorText([`Schema file ${callbackParameter.jsonName} did not parse correctly.`, err])
          }
          if (!('$schema' in schemaJson)) {
            throwWithErrorText([`Schema file is missing '$schema' keyword => ${callbackParameter.jsonName}`])
          }
        }
      },
      {
        fullScanAllFiles: true,
        skipReadFile: false
      })
      grunt.log.ok(`Total files scan: ${countScan}`)
    })
  
    grunt.registerTask('local_check_duplicate_list_in_schema-validation.json', 'Check if options list is unique in schema-validation.json', function () {
      function checkForDuplicateInList (list, listName) {
        if (list) {
          if (new Set(list).size !== list.length) {
            throwWithErrorText([`Duplicate item found in ${listName}`])
          }
        }
      }
      checkForDuplicateInList(schemaValidation.tv4test, 'tv4test[]')
      checkForDuplicateInList(schemaValidation.ajvFullStrictMode, 'ajvFullStrictMode[]')
      checkForDuplicateInList(schemaValidation.skiptest, 'skiptest[]')
      checkForDuplicateInList(schemaValidation.missingcatalogurl, 'missingcatalogurl[]')
      checkForDuplicateInList(schemaValidation.fileMatchConflict, 'fileMatchConflict[]')
  
      // Check for duplicate in options[]
      const checkList = []
      for (const item of schemaValidation.options) {
        const schemaName = Object.keys(item).pop()
        if (checkList.includes(schemaName)) {
          throwWithErrorText([`Duplicate schema name found in options[] schema-validation.json => ${schemaName}`])
        }
        // Check for all values inside one option object
        const optionValues = Object.values(item).pop()
        checkForDuplicateInList(optionValues?.unknownKeywords, `${schemaName} unknownKeywords[]`)
        checkForDuplicateInList(optionValues?.unknownFormat, `${schemaName} unknownFormat[]`)
        checkForDuplicateInList(optionValues?.externalSchema, `${schemaName} externalSchema[]`)
        checkList.push(schemaName)
      }
  
      grunt.log.ok('OK')
    })
  
    grunt.registerTask('local_check_for_test_folders_without_schema_to_be_tested', 'Check if schema file is missing', function () {
      let countTestFolders = 0
      const x = (listFolders) => {
        listFolders.forEach((folderName) => {
          if (!skipThisFileName(folderName)) {
            countTestFolders++
            if (!schemasToBeTested.includes(folderName + '.json')) {
              throwWithErrorText([`No schema ${folderName}.json found for test folder => ${folderName}`])
            }
          }
        })
      }
      x(foldersPositiveTest)
      x(foldersNegativeTest)
      grunt.log.ok(`Total test folders: ${countTestFolders}`)
    })
  
    grunt.registerTask('local_count_url_in_catalog', 'Show statistic info of the catalog', function () {
      let countScanURLExternal = 0
      let countScanURLInternal = 0
      getUrlFromCatalog(catalogUrl => {
        catalogUrl.startsWith(urlSchemaStore) ? countScanURLInternal++ : countScanURLExternal++
      })
      const totalCount = countScanURLExternal + countScanURLInternal
      const percentExternal = (countScanURLExternal / totalCount) * 100
      grunt.log.ok(`${countScanURLInternal} SchemaStore URL`)
      grunt.log.ok(`${countScanURLExternal} External URL (${Math.round(percentExternal)}%)`)
      grunt.log.ok(`${totalCount} Total URL`)
    })
  
    grunt.registerTask('local_count_schema_tested_in_full_strict_mode', 'Show statistic how many full strict schema there are', function () {
      let countSchemaScanViaAJV = 0
      localSchemaFileAndTestFile({
        schemaOnlyScan: function (callbackParameter) {
          countSchemaScanViaAJV++
        }
      })
      const countFullStrictSchema = schemaValidation.ajvFullStrictMode.length
      const percent = (countFullStrictSchema / countSchemaScanViaAJV) * 100
      grunt.log.ok('Schema in full strict mode to prevent any unexpected behaviours or silently ignored mistakes in user schemas.')
      grunt.log.ok(`${countFullStrictSchema} of ${countSchemaScanViaAJV} (${Math.round(percent)}%)`)
    })
  
    grunt.registerTask('local_tv4_validator_cannot_have_negative_test', 'Check for forbidden negative test folder', function () {
      const found = foldersNegativeTest.find((x) => schemaValidation.tv4test.includes(x + '.json'))
      if (found) {
        throwWithErrorText([`Negative folder found for TV4 validator => ${pt.join(testNegativeDir, found)}`])
      }
      grunt.log.ok('OK')
    })
  
    grunt.registerTask('local_check_in_schema-validation.json_for_missing_schema_files', 'Check if all schema JSON files are present', function () {
      let countSchemaValidationItems = 0
      const x = (list) => {
        list.forEach((schemaName) => {
          if (schemaName.endsWith('.json')) {
            countSchemaValidationItems++
            if (!schemasToBeTested.includes(schemaName)) {
              throwWithErrorText([`No schema ${schemaName} found in schema folder => ${schemaDir}`])
            }
          }
        })
      }
      x(schemaValidation.tv4test)
      x(schemaValidation.ajvFullStrictMode)
      x(schemaValidation.skiptest)
      x(schemaValidation.missingcatalogurl)
  
      for (const item of schemaValidation.options) {
        const schemaName = Object.keys(item).pop()
        if (schemaName !== 'readme_example.json') {
          countSchemaValidationItems++
          if (!schemasToBeTested.includes(schemaName)) {
            throwWithErrorText([`No schema ${schemaName} found in schema folder => ${schemaDir}`])
          }
        }
      }
      grunt.log.ok(`Total schema-validation.json items check: ${countSchemaValidationItems}`)
    })
  
    grunt.registerTask('local_coverage', 'Run one selected schema in coverage mode', function () {
      const javaScriptCoverageName = 'schema.json.translated.to.js'
      const javaScriptCoverageNameWithPath = pt.join(__dirname, `${temporaryCoverageDir}/${javaScriptCoverageName}`)
  
      /**
       * Translate one JSON schema file to javascript via AJV validator.
       * And run the positive and negative test files with it.
       * @param {string} processOnlyThisOneSchemaFile The schema file that need to process
       */
      const generateCoverage = (processOnlyThisOneSchemaFile) => {
        const standaloneCode = require('ajv/dist/standalone').default
        const schemaVersion = showSchemaVersions()
        let jsonName
        let mainSchema
        let mainSchemaJsonId
        let isThisWithExternalSchema
        let validations
  
        // Compile JSON schema to javascript and write it to disk.
        const processSchemaFile = (callbackParameter) => {
          jsonName = callbackParameter.jsonName
          // Get possible options define in schema-validation.json
          const {
            unknownFormatsList,
            unknownKeywordsList,
            externalSchemaWithPathList
          } = getOption(callbackParameter.jsonName)
  
          // select the correct AJV object for this schema
          mainSchema = JSON.parse(callbackParameter.rawFile)
          const versionObj = schemaVersion.getObj(mainSchema)
  
          // External schema present to be included?
          const multipleSchema = []
          isThisWithExternalSchema = externalSchemaWithPathList.length > 0
          if (isThisWithExternalSchema) {
            // There is an external schema that need to be included.
            externalSchemaWithPathList.forEach((x) => {
              multipleSchema.push(require(x.toString()))
            })
            // Also add the 'root' schema
            multipleSchema.push(mainSchema)
          }
  
          // Get the correct AJV version
          const ajvSelected = factoryAJV({
            schemaName: versionObj?.schemaName,
            unknownFormatsList: unknownFormatsList,
            fullStrictMode: schemaValidation.ajvFullStrictMode.includes(jsonName),
            standAloneCode: true,
            standAloneCodeWithMultipleSchema: multipleSchema
          })
  
          // AJV must ignore these keywords
          unknownKeywordsList?.forEach((x) => {
            ajvSelected.addKeyword(x)
          })
  
          let moduleCode
          if (isThisWithExternalSchema) {
            // Multiple schemas are combine to one JavaScript file.
            // Must use the root $id/id to call the correct 'main' schema in JavaScript code
            mainSchemaJsonId = schemaVersion.getObj(mainSchema).schemaName === 'draft-04' ? mainSchema.id : mainSchema.$id
            if (!mainSchemaJsonId) {
              throwWithErrorText([`Missing $id or id in ${jsonName}`])
            }
            moduleCode = standaloneCode(ajvSelected)
          } else { // Single schema
            mainSchemaJsonId = undefined
            moduleCode = standaloneCode(ajvSelected, ajvSelected.compile(mainSchema))
          }
  
          // Write the javascript module code to file + 'js-beautify' it
          fs.writeFileSync(javaScriptCoverageNameWithPath, require('js-beautify').js(moduleCode, { indent_size: 2 }))
          // Now use this JavaScript as validation in the positive and negative test
          validations = require(javaScriptCoverageNameWithPath)
        }
  
        // Load the Javascript file from the disk and run it with the JSON test file.
        // This will generate the NodeJS coverage data in the background.
        const processTestFile = (callbackParameter) => {
          // Test only for the code coverage. Not for the validity of the test.
          if (isThisWithExternalSchema) {
            // Must use the root $id/id to call the correct schema JavaScript code
            const validateRootSchema = validations[mainSchemaJsonId]
            validateRootSchema?.(JSON.parse(callbackParameter.rawFile))
          } else {
            // Single schema does not need $id
            validations(JSON.parse(callbackParameter.rawFile))
          }
        }
  
        localSchemaFileAndTestFile({
          schemaForTestScan: processSchemaFile,
          positiveTestScan: processTestFile,
          negativeTestScan: processTestFile
        }, { skipReadFile: false, processOnlyThisOneSchemaFile: processOnlyThisOneSchemaFile })
      }
  
      // Generate the schema via option parameter 'SchemaName'
      const schemaNameToBeCoverage = grunt.option('SchemaName')
      if (!schemaNameToBeCoverage) {
        throwWithErrorText(['Must start "make" file with schema name parameter.'])
      }
      // Not for tv4 schema files
      if (schemaValidation.tv4test.includes(schemaNameToBeCoverage)) {
        throwWithErrorText([`Coverage is not possible for tv4-validator schema file :${schemaNameToBeCoverage}`])
      }
      generateCoverage(schemaNameToBeCoverage)
      grunt.log.ok('OK')
    })
  
    grunt.registerTask('local_check_if_schema_is_already_in_strict_mode', 'Check if schema need to be added to strict mode', function () {
      // this is only for AJV schemas
      const schemaVersion = showSchemaVersions()
      let countSchemaTotal = 0
      let countSchemaNeedToPutInFullStrictModeListTotal = 0
      const checkIfThisSchemaIsAlreadyInStrictMode = (callbackParameter) => {
        if (schemaValidation.ajvFullStrictMode.includes(callbackParameter.jsonName)) {
          // It is already in the strict mode list
          return
        }
        countSchemaTotal++
        const {
          unknownFormatsList,
          unknownKeywordsList,
          externalSchemaWithPathList
        } = getOption(callbackParameter.jsonName)
  
        // select the correct AJV object for this schema
        const mainSchema = JSON.parse(callbackParameter.rawFile)
        const versionObj = schemaVersion.getObj(mainSchema)
  
        // Get the correct AJV version
        const ajvSelected = factoryAJV({
          schemaName: versionObj?.schemaName,
          unknownFormatsList: unknownFormatsList,
          fullStrictMode: true
        })
  
        // AJV must ignore these keywords
        unknownKeywordsList?.forEach((x) => {
          ajvSelected.addKeyword(x)
        })
  
        // Add external schema to AJV
        externalSchemaWithPathList.forEach((x) => {
          ajvSelected.addSchema(require(x.toString()))
        })
  
        try {
          ajvSelected.compile(mainSchema)
        } catch (e) {
          // failed to compile in strict mode.
          return
        }
        countSchemaNeedToPutInFullStrictModeListTotal++
        grunt.log.writeln(`${callbackParameter.jsonName}`)
      }
  
      localSchemaFileAndTestFile({
        schemaOnlyScan: checkIfThisSchemaIsAlreadyInStrictMode
      }, { skipReadFile: false })
      grunt.log.writeln()
      grunt.log.ok(`Schemas that need to be put in full strict mode list : ${countSchemaNeedToPutInFullStrictModeListTotal}`)
      grunt.log.ok(`Total schemas check: ${countSchemaTotal}`)
    })
  
    grunt.registerTask('local_test',
      [
        'local_check_duplicate_list_in_schema-validation.json',
        'local_validate_directory_structure',
        'local_check_filename_extension',
        'local_check_in_schema-validation.json_for_missing_schema_files',
        'local_check_for_test_folders_without_schema_to_be_tested',
        'local_tv4_validator_cannot_have_negative_test',
        'local_catalog',
        'local_catalog-fileMatch-conflict',
        'local_url-present-in-catalog',
        'local_schema-present-in-catalog-list',
        'local_bom',
        'local_find-duplicated-property-keys',
        'local_check_for_schema_version_present',
        'local_count_url_in_catalog',
        'local_count_schema_versions',
        'local_search_for_schema_without_positive_test_files',
        'local_count_schema_tested_in_full_strict_mode',
        'local_ajv_test',
        'local_tv4_only_for_non_compliance_schema'
      ])
    grunt.registerTask('remote_test', ['remote_count_schema_versions', 'remote_bom', 'remote_ajv_test'])
    grunt.registerTask('default', ['local_test'])
    grunt.registerTask('local_maintenance', ['local_test_downgrade_schema_version', 'local_check_if_schema_is_already_in_strict_mode'])
  }
  