@hi18n/cli
Version:
Message internationalization meets immutability and type-safety - command line tool
282 lines (226 loc) • 9.41 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sync = sync;
var _push = _interopRequireDefault(require("core-js-pure/stable/instance/push.js"));
var _utils = require("@typescript-eslint/utils");
var _nodeFs = _interopRequireDefault(require("node:fs"));
var _glob = _interopRequireDefault(require("glob"));
var _nodePath = _interopRequireDefault(require("node:path"));
var _nodeUtil = _interopRequireDefault(require("node:util"));
var _resolve = _interopRequireDefault(require("resolve"));
var _eslintPlugin = require("@hi18n/eslint-plugin");
var _config = require("./config");
async function sync(options) {
var _config$include, _config$exclude;
const {
cwd: projectPath,
include: includeFromOpt,
exclude: excludeFromOpt
} = options;
const config = await (0, _config.loadConfig)(projectPath);
const include = (_config$include = config.include) !== null && _config$include !== void 0 ? _config$include : includeFromOpt;
const exclude = (_config$exclude = config.exclude) !== null && _config$exclude !== void 0 ? _config$exclude : excludeFromOpt;
if (include === undefined || include.length === 0) {
throw new Error("No include specified");
}
const linterConfig = {
parser: config.parser,
parserOptions: config.parserOptions
};
const linter = new _utils.TSESLint.Linter({
cwd: projectPath
});
const translationUsages = [];
linter.defineRule("@hi18n/collect-translation-ids", _eslintPlugin.rules["collect-translation-ids"]);
const bookDefs = [];
linter.defineRule("@hi18n/collect-book-definitions", _eslintPlugin.rules["collect-book-definitions"]);
const catalogDefs = [];
linter.defineRule("@hi18n/collect-catalog-definitions", _eslintPlugin.rules["collect-catalog-definitions"]);
linter.defineRule("@hi18n/no-missing-translation-ids", _eslintPlugin.rules["no-missing-translation-ids"]);
linter.defineRule("@hi18n/no-unused-translation-ids", _eslintPlugin.rules["no-unused-translation-ids"]);
linter.defineRule("@hi18n/no-missing-translation-ids-in-types", _eslintPlugin.rules["no-missing-translation-ids-in-types"]);
linter.defineRule("@hi18n/no-unused-translation-ids-in-types", _eslintPlugin.rules["no-unused-translation-ids-in-types"]);
const files = [];
for (const includeGlob of include) {
(0, _push.default)(files).call(files, ...(await _nodeUtil.default.promisify(_glob.default)(includeGlob, {
cwd: projectPath,
nodir: true,
ignore: exclude
})));
}
for (const relative of files) {
const filename = _nodePath.default.join(projectPath, relative);
const source = await _nodeFs.default.promises.readFile(filename, "utf-8");
const messages = linter.verify(source, { ...linterConfig,
rules: {
"@hi18n/collect-translation-ids": ["error", u => {
(0, _push.default)(translationUsages).call(translationUsages, u);
}],
"@hi18n/collect-book-definitions": ["error", b => {
(0, _push.default)(bookDefs).call(bookDefs, b);
}],
"@hi18n/collect-catalog-definitions": ["error", c => {
(0, _push.default)(catalogDefs).call(catalogDefs, c);
}]
}
}, {
filename
});
checkMessages(relative, messages);
}
const linkage = {};
const usedTranslationIds = {};
const rewriteTargetFiles = new Set();
for (const u of translationUsages) {
const loc = u.bookLocation;
if (loc.path !== undefined) {
const {
resolved
} = await resolveWithFallback(removeExtension(loc.path, config.extensionsToRemove), {
basedir: _nodePath.default.dirname(loc.base),
extensions: config.extensions
}, config.baseUrl, config.paths);
loc.path = resolved;
}
const locName = (0, _eslintPlugin.serializeReference)(loc);
if (hasOwn(usedTranslationIds, locName)) {
var _context;
(0, _push.default)(_context = usedTranslationIds[locName]).call(_context, u.id);
} else {
setRecordValue(usedTranslationIds, locName, [u.id]);
}
rewriteTargetFiles.add(loc.path !== undefined ? loc.path : loc.base);
}
for (const bookDef of bookDefs) {
const bookLocNames = (0, _eslintPlugin.serializedLocations)(bookDef.bookLocation);
const concatenatedTranslationIds = bookLocNames.flatMap(locName => hasOwn(usedTranslationIds, locName) ? usedTranslationIds[locName] : []);
const uniqueTranslationIds = Array.from(new Set(concatenatedTranslationIds)).sort();
for (const locName of bookLocNames) {
setRecordValue(usedTranslationIds, locName, uniqueTranslationIds);
}
const primaryName = bookLocNames[0];
for (const catalogLink of bookDef.catalogLinks) {
const loc = catalogLink.catalogLocation;
if (loc.path !== undefined) {
const {
resolved
} = await resolveWithFallback(removeExtension(loc.path, config.extensionsToRemove), {
basedir: _nodePath.default.dirname(loc.base),
extensions: config.extensions
}, config.baseUrl, config.paths);
loc.path = resolved;
}
setRecordValue(linkage, (0, _eslintPlugin.serializeReference)(loc), primaryName);
rewriteTargetFiles.add(loc.path !== undefined ? loc.path : loc.base);
}
rewriteTargetFiles.add(bookDef.bookLocation.path);
}
const valueHints = {}; // Set up passive importing
if (config.connector) {
const c = config.connector.connector(config.configPath, config.connectorOptions);
if (c.importData) {
const data = await c.importData();
for (const [locale, catalog] of Object.entries(data.translations)) {
var _valueHints$locale;
const vhCatalog = (_valueHints$locale = valueHints[locale]) !== null && _valueHints$locale !== void 0 ? _valueHints$locale : valueHints[locale] = {};
for (const [id, msg] of Object.entries(catalog)) {
var _vhCatalog$id;
(_vhCatalog$id = vhCatalog[id]) !== null && _vhCatalog$id !== void 0 ? _vhCatalog$id : vhCatalog[id] = msg.raw;
}
}
}
}
for (const rewriteTargetFile of Array.from(rewriteTargetFiles).sort()) {
const source = await _nodeFs.default.promises.readFile(rewriteTargetFile, "utf-8");
const report = linter.verifyAndFix(source, { ...linterConfig,
rules: {
"@hi18n/no-missing-translation-ids": "warn",
"@hi18n/no-unused-translation-ids": "warn",
"@hi18n/no-missing-translation-ids-in-types": "warn",
"@hi18n/no-unused-translation-ids-in-types": "warn"
},
settings: {
"@hi18n/linkage": linkage,
"@hi18n/used-translation-ids": usedTranslationIds,
"@hi18n/value-hints": valueHints
}
}, {
filename: rewriteTargetFile
});
checkMessages(rewriteTargetFile, report.messages);
if (report.fixed) {
if (options.checkOnly) {
throw new Error(`Found diff in ${_nodePath.default.relative(projectPath, rewriteTargetFile)}`);
}
await _nodeFs.default.promises.writeFile(rewriteTargetFile, report.output, "utf-8");
}
}
}
function checkMessages(filepath, messages) {
for (const message of messages) {
if (/^Definition for rule .* was not found\.$/.test(message.message)) {
// We load ESLint with minimal rules. Ignore the "missing rule" error.
continue;
}
if (message.severity >= 2) throw new Error(`Error on ${filepath}: ${message.message}`);
}
}
function removeExtension(id, extensionsToRemove) {
for (const ext of extensionsToRemove) {
if (id.endsWith(ext)) {
return id.substring(0, id.length - ext.length);
}
}
return id;
}
async function resolveWithFallback(id, opts, baseUrl, paths) {
if (baseUrl && isPackageLikePath(id)) {
const matchers = Object.entries(paths !== null && paths !== void 0 ? paths : {});
(0, _push.default)(matchers).call(matchers, ["*", ["*"]]);
for (const [matcher, candidates] of matchers) {
let replacement = undefined;
if (id === matcher) {
replacement = "";
} else if (matcher.endsWith("*") && id.startsWith(matcher.substring(0, matcher.length - 1))) {
replacement = id.substring(matcher.length - 1);
}
if (replacement === undefined) continue;
for (const candidate of candidates) {
try {
return await resolveAsPromise(_nodePath.default.resolve(baseUrl, candidate.replace("*", replacement)), opts);
} catch (_e) {// Likely MODULE_NOT_FOUND
}
}
}
}
return await resolveAsPromise(id, opts);
}
function resolveAsPromise(id, opts) {
return new Promise((resolvePromise, rejectPromise) => {
(0, _resolve.default)(id, opts, (err, resolved, pkg) => {
if (err) rejectPromise(err);else resolvePromise({
resolved: resolved,
pkg
});
});
});
}
function isPackageLikePath(p) {
const firstSegment = p.split(/[/\\]/)[0];
return firstSegment !== "." && firstSegment !== ".." && !_nodePath.default.isAbsolute(p);
}
function hasOwn(record, key) {
return Object.prototype.hasOwnProperty.call(record, key);
}
function setRecordValue(record, key, value) {
Object.defineProperty(record, key, {
value,
writable: true,
configurable: true,
enumerable: true
});
}
//# sourceMappingURL=sync.js.map