mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-01 01:38:07 +08:00
Modifying some scripts that verify the shape of localization calls to work with the i18nString shorthand: const i18nString = i18.i18n.getLocalizedString.bind(undefined, str_); Bug: 1136655 Change-Id: I09a961136dd89e8af11ab33f413f315e61184078 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2469903 Commit-Queue: Vidal Diazleal <vidorteg@microsoft.com> Reviewed-by: Simon Zünd <szuend@chromium.org> Reviewed-by: Peter Marshall <petermarshall@chromium.org> Reviewed-by: Christy Chen <chrche@microsoft.com>
179 lines
6.3 KiB
JavaScript
179 lines
6.3 KiB
JavaScript
// Copyright 2020 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/**
|
|
* This script is part of the presubmit check that parses DevTools frontend files,
|
|
* collects localizable strings, and run some checks for localization.
|
|
*
|
|
* If argument '--autofix' is present, try fixing the error automatically
|
|
*/
|
|
const fs = require('fs');
|
|
const ts = require('typescript');
|
|
const writeFileAsync = fs.promises.writeFile;
|
|
const parseLocalizableResources = require('./utils/check_localized_strings');
|
|
|
|
/**
|
|
* Verifies that all strings in UIStrings structure are called with localization API.
|
|
*/
|
|
async function checkUIStrings(shouldAutoFix) {
|
|
const localizationCallsMap = parseLocalizableResources.localizationCallsMap;
|
|
const uiStringsMap = parseLocalizableResources.uiStringsMap;
|
|
const errorMap = new Map();
|
|
for (const [filePath, uiStringsEntries] of uiStringsMap.entries()) {
|
|
let errorList;
|
|
if (filePath.endsWith('ModuleUIStrings.js')) {
|
|
const newFilePath = filePath.replace('ModuleUIStrings.js', 'module.json');
|
|
const stringIdSet = getStringIdsFromCallSites(localizationCallsMap.get(newFilePath));
|
|
errorList = checkStringEntries(uiStringsEntries, stringIdSet, true);
|
|
} else {
|
|
const stringIdSet = getStringIdsFromCallSites(localizationCallsMap.get(filePath));
|
|
errorList = checkStringEntries(uiStringsEntries, stringIdSet, false);
|
|
}
|
|
|
|
if (errorList.length > 0) {
|
|
errorMap.set(filePath, errorList);
|
|
}
|
|
}
|
|
|
|
if (errorMap.size > 0) {
|
|
if (shouldAutoFix) {
|
|
return autoFixUIStringsCheck(errorMap);
|
|
}
|
|
return addUIStringsCheckError(errorMap);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Get all the string entries called with localization API from the entry map of that file.
|
|
* Returns a set of the string IDs.
|
|
*/
|
|
function getStringIdsFromCallSites(entryFromCallsMap) {
|
|
const stringIdSet = new Set();
|
|
if (entryFromCallsMap) {
|
|
for (const entry of entryFromCallsMap) {
|
|
stringIdSet.add(entry.stringId);
|
|
}
|
|
}
|
|
return stringIdSet;
|
|
}
|
|
|
|
/**
|
|
* Check if any unused string is in UIStrings structure.
|
|
*/
|
|
function checkStringEntries(uiStringsEntries, stringIdSet, isModuleJSON) {
|
|
const unusedEntries = [];
|
|
for (const stringEntry of uiStringsEntries) {
|
|
if (isModuleJSON) {
|
|
if (!stringIdSet.has(stringEntry.stringValue)) {
|
|
unusedEntries.push(stringEntry);
|
|
}
|
|
} else {
|
|
if (!stringIdSet.has(stringEntry.stringId)) {
|
|
unusedEntries.push(stringEntry);
|
|
}
|
|
}
|
|
}
|
|
return unusedEntries;
|
|
}
|
|
|
|
/**
|
|
* Add UIStrings check error message to the Loc V2 check error.
|
|
*/
|
|
function addUIStringsCheckError(errorMap) {
|
|
let UIStringsCheckErrorMessage = 'Unused string found in UIStrings.\n' +
|
|
'Please remove them from UIStrings, or add the localization calls in your code.\n\n';
|
|
|
|
for (const [filePath, uiStringsEntries] of errorMap.entries()) {
|
|
UIStringsCheckErrorMessage += `${filePath}\n`;
|
|
for (const entry of uiStringsEntries) {
|
|
UIStringsCheckErrorMessage += ` "${entry.stringValue}"\n`;
|
|
}
|
|
}
|
|
return UIStringsCheckErrorMessage;
|
|
}
|
|
|
|
/**
|
|
* Auto-fixing UIString check error by removing unused strings in UIStrings structure.
|
|
*/
|
|
async function autoFixUIStringsCheck(errorMap) {
|
|
let autoFixUIStringsMessage = '\nUnused string found in UIStrings.';
|
|
const promises = [];
|
|
for (const [filePath, unusedUIStringsEntries] of errorMap.entries()) {
|
|
let content = fs.readFileSync(filePath, 'utf8');
|
|
content = removeUnusedEntries(filePath, content, unusedUIStringsEntries);
|
|
promises.push(writeFileAsync(filePath, content));
|
|
autoFixUIStringsMessage += `\nReplaced UIStrings in ${filePath}`;
|
|
}
|
|
await Promise.all(promises);
|
|
return autoFixUIStringsMessage;
|
|
}
|
|
|
|
/**
|
|
* Remove unused entries from UIStrings and return the new file content
|
|
*/
|
|
function removeUnusedEntries(filePath, content, unusedUIStringsEntries) {
|
|
const textToRemoveList = getTextToRemove(filePath, content, unusedUIStringsEntries);
|
|
for (const text of textToRemoveList) {
|
|
// check if the trailing comma present (the last entry may or may not have it)
|
|
if (content[content.indexOf(text) + text.length] === ',') {
|
|
content = content.replace(`${text},`, '');
|
|
} else {
|
|
content = content.replace(text, '');
|
|
}
|
|
}
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Find the full text of unused entries in UIStrings
|
|
*/
|
|
function getTextToRemove(filePath, content, unusedUIStringsEntries) {
|
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.ESNext, true);
|
|
const unusedStringIds = new Set(unusedUIStringsEntries.map(entry => entry.stringId));
|
|
const collectUnusedPropertyTextsFromNode = node => {
|
|
// check through the properties to see if the name matches a stringId that should be removed
|
|
const unusedPropertyTexts = [];
|
|
for (const property of node.initializer.properties) {
|
|
if (unusedStringIds.has(property.name.escapedText)) {
|
|
// get the full text of the entry including descriptions and placeholders
|
|
unusedPropertyTexts.push(property.getFullText());
|
|
}
|
|
}
|
|
return unusedPropertyTexts;
|
|
};
|
|
|
|
const uiStringsNode = parseLocalizableResources.findUIStringsNode(sourceFile);
|
|
return collectUnusedPropertyTextsFromNode(uiStringsNode);
|
|
}
|
|
|
|
/**
|
|
* Verifies that there are no V1 APIs added in a directories that are migrated.
|
|
* The check will be removed when the migration process is done.
|
|
*/
|
|
function checkNoV1CallsInMigratedDir() {
|
|
const filesContainV1Calls = parseLocalizableResources.locV1CallsInMigratedFiles;
|
|
if (filesContainV1Calls.size === 0) {
|
|
return;
|
|
}
|
|
|
|
fileMigratedError = 'Localization V1 APIs used in these files that have already migrated to V2:\n';
|
|
for (const filePath of filesContainV1Calls) {
|
|
fileMigratedError += `\n${filePath}`;
|
|
}
|
|
fileMigratedError += '\nAutofix are not supported for this check. Please manually update V1 APIs to V2 APIs.';
|
|
fileMigratedError += `\nFor example:
|
|
ls("An example string") ---> i18nString(UIStrings.theExampleString)
|
|
and then add it to UIStrings:
|
|
const UIStrings = { theExampleString: 'An example string' } with descriptions.`;
|
|
fileMigratedError += '\nFor more details. See devtools-frontend\\src\\docs\\localization\\README.md';
|
|
return fileMigratedError;
|
|
}
|
|
|
|
module.exports = {
|
|
checkUIStrings,
|
|
removeUnusedEntries,
|
|
checkNoV1CallsInMigratedDir,
|
|
};
|