diff --git a/README.md b/README.md index 4b86b7e..e8e837a 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,8 @@ simple }; ``` + - `checkDuplicateValues`: Boolean (Optional). Default value: `false`. If true, the values will also be checked for duplicates, in comparison to the file specified in `filePath`. + Output from the slightly advanced [identical keys](/examples/multiple-keys-per-locale) example where some keys from the reference translation file (`en-US`) were not found during comparison. ![](assets/identical-keys-error-screenshot-2018-04-30.png) diff --git a/src/__snapshots__/identical-keys.test.js.snap b/src/__snapshots__/identical-keys.test.js.snap index ab1961f..4521ad9 100644 --- a/src/__snapshots__/identical-keys.test.js.snap +++ b/src/__snapshots__/identical-keys.test.js.snap @@ -20,6 +20,24 @@ exports[`Snapshot Tests for Invalid Code map of comparison files - structure mis }" `; +exports[`Snapshot Tests for Invalid Code single comparison file - duplicate values 1`] = ` +" +- Expected ++ Received + +@@ -2,7 +2,7 @@ + \\"translationLevelOne\\": Object { +- \\"translationKeyA\\": \\"Message\\", ++ \\"translationKeyA\\": \\"value a\\", + \\"translationLevelTwo\\": Object { +- \\"translationKeyB\\": \\"Message\\", ++ \\"translationKeyB\\": \\"value b\\", + \\"translationsLevelThree\\": Object { +- \\"translationKeyC\\": \\"Message\\", ++ \\"translationKeyC\\": \\"value c\\", + }," +`; + exports[`Snapshot Tests for Invalid Code single comparison file - structure mismatch 1`] = ` " - Expected diff --git a/src/identical-keys.js b/src/identical-keys.js index f941b99..f069937 100644 --- a/src/identical-keys.js +++ b/src/identical-keys.js @@ -34,6 +34,11 @@ const getKeyStructureFromMap = (filePathMap, sourceFilePath) => { If it's an object , then it should have a mapping b/w file names and what key structure file to require. + + checkDuplicateValues = boolean + + If true, the values will also be checked for duplicates, + in comparison to the file specified in filePath. } */ @@ -159,10 +164,13 @@ const identicalKeys = (context, source, sourceFilePath) => { return errors; } + const { checkDuplicateValues = false } = comparisonOptions; + const isSourceFile = sourceFilePath === comparisonOptions.filePath; const diffString = compareTranslationsStructure( settings, keyStructure, - currentTranslations + currentTranslations, + checkDuplicateValues && !isSourceFile ); if (noDifferenceRegex.test(diffString.trim())) { @@ -196,6 +204,9 @@ module.exports = { properties: { filePath: { type: ['string', 'object'] + }, + checkDuplicateValues: { + type: 'boolean' } }, type: 'object' diff --git a/src/identical-keys.test.js b/src/identical-keys.test.js index 7c48a0d..73f830a 100644 --- a/src/identical-keys.test.js +++ b/src/identical-keys.test.js @@ -94,6 +94,29 @@ ruleTester.run('identical-keys', rule, { ], filename: 'file.json' }, + // single file path to compare with, while checking for duplicate values + { + code: ` + /*{ + "translationLevelOne": { + "translationKeyA": "translated value a", + "translationLevelTwo": { + "translationKeyB": "translated value b", + "translationsLevelThree": { + "translationKeyC": "translated value c" + } + } + } + }*//*path/to/file.json*/ + `, + options: [ + { + filePath: 'path/to/compare-file-a.json', + checkDuplicateValues: true + } + ], + filename: 'file.json' + }, // mapping to match which file we should use to compare structure { code: ` @@ -293,6 +316,31 @@ describe('Snapshot Tests for Invalid Code', () => { }); expect(strip(errors[0].message)).toMatchSnapshot(); }); + test('single comparison file - duplicate values', () => { + const errors = run({ + code: ` + /*{ + "translationLevelOne": { + "translationKeyA": "value a", + "translationLevelTwo": { + "translationKeyB": "value b", + "translationsLevelThree": { + "translationKeyC": "value c" + } + } + } + }*//*/path/to/invalid-file.json*/ + `, + options: [ + { + filePath: 'path/to/compare-file-a.json', + checkDuplicateValues: true + } + ], + filename: 'file.json' + }); + expect(strip(errors[0].message)).toMatchSnapshot(); + }); test('map of comparison files - structure mismatch', () => { const errors = run({ code: ` diff --git a/src/util/compare-translations-structure.js b/src/util/compare-translations-structure.js index c4b5a68..bebfd89 100644 --- a/src/util/compare-translations-structure.js +++ b/src/util/compare-translations-structure.js @@ -1,4 +1,5 @@ const set = require('lodash.set'); +const get = require('lodash.get'); const diff = require('jest-diff'); const deepForOwn = require('./deep-for-own'); @@ -7,11 +8,18 @@ const DIFF_OPTIONS = { contextLines: 1 }; +const noDifferenceRegex = /Compared\s+values\s+have\s+no\s+visual\s+difference/i; + // we don't care what the actual values are. // lodash.set will automatically convert a previous string value // into an object, if the current path states that a key is nested inside. // reminder, deepForOwn goes from the root level to the deepest level (preorder) -const compareTranslationsStructure = (settings, translationsA, translationsB) => { +const compareTranslationsStructure = ( + settings, + translationsA, + translationsB, + checkDuplicateValues +) => { const augmentedTranslationsA = {}; const augmentedTranslationsB = {}; @@ -21,13 +29,27 @@ const compareTranslationsStructure = (settings, translationsA, translationsB) => ignorePaths }; - deepForOwn(translationsA, (value, key, path) => { + const duplicateTranslations = {}; + + deepForOwn(translationsA, (valueA, key, path) => { set(augmentedTranslationsA, path, 'Message'); }, opts); - deepForOwn(translationsB, (value, key, path) => { - set(augmentedTranslationsB, path, 'Message'); + deepForOwn(translationsB, (valueB, key, path) => { + let newValue = 'Message'; + if (checkDuplicateValues) { + set(duplicateTranslations, path, newValue); + const valueA = get(translationsA, path); + if (valueA === valueB) { + newValue = valueB; + } + } + set(augmentedTranslationsB, path, newValue); }, opts); - return diff(augmentedTranslationsA, augmentedTranslationsB, DIFF_OPTIONS); + const diffString = diff(augmentedTranslationsA, augmentedTranslationsB, DIFF_OPTIONS); + if (checkDuplicateValues && noDifferenceRegex.test(diffString.trim())) { + return diff(augmentedTranslationsB, duplicateTranslations, DIFF_OPTIONS); + } + return diffString; }; module.exports = compareTranslationsStructure;