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 log dynamic translation lookups (in HBS files) #514

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
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
Prev Previous commit
Add option to log dynamic translation lookups
  • Loading branch information
robinborst95 committed May 26, 2022
commit ac0e717ef694a3170a93e8b2c8c8ce748c4670fa
5 changes: 5 additions & 0 deletions __snapshots__/test.js.snap
Original file line number Diff line number Diff line change
@@ -3,6 +3,11 @@
exports[`Test Fixtures concat-expression 1`] = `
"[1/4] 🔍 Finding JS and HBS files...
[2/4] 🔍 Searching for translations keys in JS and HBS files...

⭐ Found 2 dynamic translations. This might cause this tool to report more unused translations than there actually are!

- prefix.{{this.dynamicKey}}.not-missing (used in app/templates/application.hbs)
- prefix.{{this.dynamicKey}}.value (used in app/templates/application.hbs)
[3/4] ⚙️ Checking for unused translations...
[4/4] ⚙️ Checking for missing translations...

5 changes: 4 additions & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
@@ -8,7 +8,10 @@ const { run } = require('../index');

let rootDir = pkgDir.sync();

run(rootDir, { fix: process.argv.includes('--fix') })
run(rootDir, {
fix: process.argv.includes('--fix'),
logDynamic: process.argv.includes('--log-dynamic'),
})
.then(exitCode => {
process.exitCode = exitCode;
})
109 changes: 98 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ async function run(rootDir, options = {}) {
let log = options.log || console.log;
let writeToFile = options.writeToFile || fs.writeFileSync;
let shouldFix = options.fix || false;
let logDynamic = options.logDynamic || false;

let chalkOptions = {};
if (options.color === false) {
@@ -33,7 +34,26 @@ async function run(rootDir, options = {}) {
let files = [...appFiles, ...inRepoFiles];

log(`${step(2)} 🔍 Searching for translations keys in JS and HBS files...`);
let usedTranslationKeys = await analyzeFiles(rootDir, files);
let [usedTranslationKeys, usedDynamicTranslations] = await analyzeFiles(rootDir, files);

if (logDynamic) {
if (usedDynamicTranslations.size === 0) {
log();
log(' ⭐ No dynamic translations were found.');
log();
} else {
log();
log(
` ⭐ Found ${chalk.bold.yellow(
usedDynamicTranslations.size
)} dynamic translations. This might cause this tool to report more unused translations than there actually are!`
);
log();
for (let [key, files] of usedDynamicTranslations) {
log(` - ${key} ${chalk.dim(`(used in ${generateFileList(files)})`)}`);
}
}
}

log(`${step(3)} ⚙️ Checking for unused translations...`);

@@ -163,9 +183,10 @@ function joinPaths(inputPathOrPaths, outputPaths) {

async function analyzeFiles(cwd, files) {
let allTranslationKeys = new Map();
let allDynamicTranslations = new Map();

for (let file of files) {
let translationKeys = await analyzeFile(cwd, file);
let [translationKeys, dynamicTranslations] = await analyzeFile(cwd, file);

for (let key of translationKeys) {
if (allTranslationKeys.has(key)) {
@@ -174,9 +195,17 @@ async function analyzeFiles(cwd, files) {
allTranslationKeys.set(key, new Set([file]));
}
}

for (let dynamicTranslation of dynamicTranslations) {
if (allDynamicTranslations.has(dynamicTranslation)) {
allDynamicTranslations.get(dynamicTranslation).add(file);
} else {
allDynamicTranslations.set(dynamicTranslation, new Set([file]));
}
}
}

return allTranslationKeys;
return [allTranslationKeys, allDynamicTranslations];
}

async function analyzeFile(cwd, file) {
@@ -229,24 +258,78 @@ async function analyzeJsFile(content) {
},
});

return translationKeys;
return [translationKeys, new Set()];
}

async function analyzeHbsFile(content) {
let translationKeys = new Set();
let dynamicTranslations = new Set();

// parse the HBS file
let ast = Glimmer.preprocess(content);

class StringKey {
constructor(value) {
this.value = value;
}

join(otherKey) {
if (otherKey instanceof StringKey) {
return new StringKey(this.value + otherKey.value);
} else {
return new CompositeKey(this, otherKey);
}
}

toString() {
return this.value;
}
}

class DynamicKey {
constructor(node) {
this.node = node;
}

join(otherKey) {
return new CompositeKey(this, otherKey);
}

toString() {
if (this.node.type === 'PathExpression') {
return `{{${this.node.original}}}`;
} else if (this.node.type === 'SubExpression') {
return `{{${this.node.path.original} helper}}`;
}

return '{{dynamic key}}';
}
}

class CompositeKey {
constructor(...values) {
this.values = values;
}

join(otherKey) {
return new CompositeKey(...this.values, otherKey);
}

toString() {
return this.values.reduce((string, value) => string + value.toString(), '');
}
}

function findKeysInIfExpression(node) {
let keysInFirstParam = findKeysInNode(node.params[1]);
let keysInSecondParam = node.params.length > 2 ? findKeysInNode(node.params[2]) : [''];
let keysInSecondParam =
node.params.length > 2 ? findKeysInNode(node.params[2]) : [new StringKey('')];

return [...keysInFirstParam, ...keysInSecondParam];
}

function findKeysInConcatExpression(node) {
let potentialKeys = [''];
let potentialKeys = [new StringKey('')];

for (let param of node.params) {
let keysInParam = findKeysInNode(param);
@@ -255,7 +338,7 @@ async function analyzeHbsFile(content) {

potentialKeys = potentialKeys.reduce((newPotentialKeys, potentialKey) => {
for (let key of keysInParam) {
newPotentialKeys.push(potentialKey + key);
newPotentialKeys.push(potentialKey.join(key));
}

return newPotentialKeys;
@@ -269,14 +352,14 @@ async function analyzeHbsFile(content) {
if (!node) return [];

if (node.type === 'StringLiteral') {
return [node.value];
return [new StringKey(node.value)];
} else if (node.type === 'SubExpression' && node.path.original === 'if') {
return findKeysInIfExpression(node);
} else if (node.type === 'SubExpression' && node.path.original === 'concat') {
return findKeysInConcatExpression(node);
}

return [];
return [new DynamicKey(node)];
}

function processNode(node) {
@@ -285,7 +368,11 @@ async function analyzeHbsFile(content) {
if (node.params.length === 0) return;

for (let key of findKeysInNode(node.params[0])) {
translationKeys.add(key);
if (key instanceof StringKey) {
translationKeys.add(key.value);
} else {
dynamicTranslations.add(key.toString());
}
}
}

@@ -302,7 +389,7 @@ async function analyzeHbsFile(content) {
},
});

return translationKeys;
return [translationKeys, dynamicTranslations];
}

async function analyzeTranslationFiles(cwd, files) {
2 changes: 2 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ describe('Test Fixtures', () => {
'external-addon-translations',
];
let fixturesWithFix = ['remove-unused-translations', 'remove-unused-translations-nested'];
let fixturesWithDynamicKeys = ['concat-expression'];
let fixturesWithConfig = {
'external-addon-translations': {
externalPaths: ['@*/*', 'external-addon'],
@@ -41,6 +42,7 @@ describe('Test Fixtures', () => {
color: false,
writeToFile,
config: fixturesWithConfig[fixture],
logDynamic: fixturesWithDynamicKeys.includes(fixture),
});

let expectedReturnValue = fixturesWithErrors.includes(fixture) ? 1 : 0;