Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to check for duplicate values to identical-keys #33

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions src/__snapshots__/identical-keys.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>\\",
+ \\"translationKeyA\\": \\"value a\\",
\\"translationLevelTwo\\": Object {
- \\"translationKeyB\\": \\"Message<String>\\",
+ \\"translationKeyB\\": \\"value b\\",
\\"translationsLevelThree\\": Object {
- \\"translationKeyC\\": \\"Message<String>\\",
+ \\"translationKeyC\\": \\"value c\\",
},"
`;

exports[`Snapshot Tests for Invalid Code single comparison file - structure mismatch 1`] = `
"
- Expected
Expand Down
13 changes: 12 additions & 1 deletion src/identical-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
*/

Expand Down Expand Up @@ -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())) {
Expand Down Expand Up @@ -196,6 +204,9 @@ module.exports = {
properties: {
filePath: {
type: ['string', 'object']
},
checkDuplicateValues: {
type: 'boolean'
}
},
type: 'object'
Expand Down
48 changes: 48 additions & 0 deletions src/identical-keys.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: `
Expand Down Expand Up @@ -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: `
Expand Down
32 changes: 27 additions & 5 deletions src/util/compare-translations-structure.js
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -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 = {};

Expand All @@ -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<String>');
}, opts);
deepForOwn(translationsB, (value, key, path) => {
set(augmentedTranslationsB, path, 'Message<String>');
deepForOwn(translationsB, (valueB, key, path) => {
let newValue = 'Message<String>';
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;