UNPKG

eslint-plugin-i18n-json

Version:

Fully extendable eslint plugin for JSON i18n translation files.

223 lines (202 loc) 5.43 kB
const requireNoCache = require('./util/require-no-cache'); const compareTranslationsStructure = require('./util/compare-translations-structure'); const getTranslationFileSource = require('./util/get-translation-file-source'); const noDifferenceRegex = /Compared\s+values\s+have\s+no\s+visual\s+difference/i; // suffix match each key in the mapping with the current source file path. // pick the first match. const getKeyStructureFromMap = (filePathMap, sourceFilePath) => { // do a suffix match const match = Object.keys(filePathMap) .filter(filePath => sourceFilePath.endsWith(filePath)) .pop(); if (match) { try { const filepath = filePathMap[match]; return requireNoCache(filepath); } catch (e) { throw new Error(`\n Error parsing or retrieving key structure comparison file based on "filePath" mapping\n\n "${match}" => "${filePathMap[match]}".\n\n Check the "filePath" option for this rule. \n ${e}`); } } throw new Error('\n Current translation file does not have a matching entry in the "filePath" map.\n Check the "filePath" option for this rule.\n'); }; /* comparisonOptions : { filePath = (string | Function | Object) If it's a string, then it can be a file to require in order to compare it's key structure with the current translation file. - If the required value is a function, then the function is called with the sourceFilePath and parsed translations to retreive the key structure. If it's an object , then it should have a mapping b/w file names and what key structure file to require. } */ const getKeyStructureToMatch = ( options = {}, currentTranslations, sourceFilePath ) => { let keyStructure = null; let { filePath } = options; if (typeof filePath === 'string') { filePath = filePath.trim(); } if (!filePath) { return { errors: [ { message: '"filePath" rule option not specified.', loc: { start: { line: 0, col: 0 } } } ] }; } if (typeof filePath === 'string') { try { keyStructure = requireNoCache(filePath); //eslint-disable-line } catch (e) { return { errors: [ { message: `\n Error parsing or retrieving key structure comparison file from\n "${filePath}".\n Check the "filePath" option for this rule.\n ${e}`, loc: { start: { line: 0, col: 0 } } } ] }; } if (typeof keyStructure !== 'function') { return { keyStructure }; } // keyStructure exported a function try { return { keyStructure: keyStructure(currentTranslations, sourceFilePath) }; } catch (e) { return { errors: [ { message: `\n Error when calling custom key structure function from\n "${filePath}".\n Check the "filePath" option for this rule.\n ${e}`, loc: { start: { line: 0, col: 0 } } } ] }; } } // due to eslint rule schema, we can assume the "filePath" option is an object. // anything else will be caught by the eslint rule schema validator. try { return { keyStructure: getKeyStructureFromMap(filePath, sourceFilePath) }; } catch (e) { return { errors: [ { message: `${e}`, loc: { start: { line: 0, col: 0 } } } ] }; } }; const identicalKeys = (context, source, sourceFilePath) => { const { options, settings = {} } = context; const comparisonOptions = options[0]; let currentTranslations = null; try { currentTranslations = JSON.parse(source); } catch (e) { // don't return any errors // will be caught with the valid-json rule. return []; } const { errors, keyStructure } = getKeyStructureToMatch( comparisonOptions, currentTranslations, sourceFilePath ); if (errors) { // errors generated from trying to get the key structure return errors; } const diffString = compareTranslationsStructure( settings, keyStructure, currentTranslations ); if (noDifferenceRegex.test(diffString.trim())) { // success return []; } // mismatch return [ { message: `\n${diffString}`, loc: { start: { line: 0, col: 0 } } } ]; }; module.exports = { meta: { docs: { category: 'Consistency', description: 'Verifies the key structure for the translation file matches the key structure specified in the options', recommended: false }, schema: [ { properties: { filePath: { type: ['string', 'object'] } }, type: 'object' } ] }, create(context) { return { Program(node) { const { valid, source, sourceFilePath } = getTranslationFileSource({ context, node }); if (!valid) { return; } const errors = identicalKeys(context, source, sourceFilePath); errors.forEach((error) => { context.report(error); }); } }; } };