UNPKG

@hi18n/cli

Version:

Message internationalization meets immutability and type-safety - command line tool

234 lines 10.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sync = sync; const utils_1 = require("@typescript-eslint/utils"); const node_fs_1 = __importDefault(require("node:fs")); const glob_1 = require("glob"); const node_path_1 = __importDefault(require("node:path")); const resolve_1 = __importDefault(require("resolve")); const internal_rules_1 = require("@hi18n/eslint-plugin/internal-rules"); const config_ts_1 = require("./config.js"); async function sync(options) { const { cwd: projectPath, include: includeFromOpt, exclude: excludeFromOpt, } = options; const config = await (0, config_ts_1.loadConfig)(projectPath); const include = config.include ?? includeFromOpt; const exclude = config.exclude ?? excludeFromOpt; if (include === undefined || include.length === 0) { throw new Error("No include specified"); } const translationUsages = []; const bookDefs = []; const catalogDefs = []; const linterConfig = { languageOptions: { parser: config.parser, parserOptions: config.parserOptions, }, plugins: { "@hi18n": { rules: { "collect-translation-ids": (0, internal_rules_1.getCollectTranslationIdsRule)((record) => translationUsages.push(record)), "collect-book-definitions": (0, internal_rules_1.getCollectBookDefinitionsRule)((record) => bookDefs.push(record)), "collect-catalog-definitions": (0, internal_rules_1.getCollectCatalogDefinitionsRule)((record) => catalogDefs.push(record)), "no-missing-translation-ids": internal_rules_1.noMissingTranslationIdsRule, "no-unused-translation-ids": internal_rules_1.noUnusedTranslationIdsRule, "no-missing-translation-ids-in-types": internal_rules_1.noMissingTranslationIdsInTypesRule, "no-unused-translation-ids-in-types": internal_rules_1.noUnusedTranslationIdsInTypesRule, }, }, }, }; const linter = new utils_1.TSESLint.Linter({ cwd: projectPath }); const files = []; for (const includeGlob of include) { files.push(...(await (0, glob_1.glob)(includeGlob, { cwd: projectPath, nodir: true, ignore: exclude ?? [], }))); } for (const relative of files) { const filename = node_path_1.default.join(projectPath, relative); const source = await node_fs_1.default.promises.readFile(filename, "utf-8"); const messages = linter.verify(source, [ { files: ["**"] }, { ...linterConfig, rules: { "@hi18n/collect-translation-ids": "error", "@hi18n/collect-book-definitions": "error", "@hi18n/collect-catalog-definitions": "error", }, }, ], { 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: node_path_1.default.dirname(loc.base), extensions: config.extensions, }, config.baseUrl, config.paths); loc.path = resolved; } const locName = (0, internal_rules_1.serializeReference)(loc); if (hasOwn(usedTranslationIds, locName)) { usedTranslationIds[locName].push(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, internal_rules_1.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: node_path_1.default.dirname(loc.base), extensions: config.extensions, }, config.baseUrl, config.paths); loc.path = resolved; } setRecordValue(linkage, (0, internal_rules_1.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)) { const vhCatalog = (valueHints[locale] ??= {}); for (const [id, msg] of Object.entries(catalog)) { vhCatalog[id] ??= msg.raw; } } } } for (const rewriteTargetFile of Array.from(rewriteTargetFiles).sort()) { const original = await node_fs_1.default.promises.readFile(rewriteTargetFile, "utf-8"); let current = await node_fs_1.default.promises.readFile(rewriteTargetFile, "utf-8"); function applyFix(rules) { const report = linter.verifyAndFix(current, [ { files: ["**"] }, { ...linterConfig, rules, settings: { "@hi18n/linkage": linkage, "@hi18n/used-translation-ids": usedTranslationIds, "@hi18n/value-hints": valueHints, }, }, ], { filename: rewriteTargetFile }); checkMessages(rewriteTargetFile, report.messages); if (report.fixed) { current = report.output; } return report.fixed; } applyFix({ "@hi18n/no-unused-translation-ids": "warn" }); applyFix({ "@hi18n/no-unused-translation-ids-in-types": "warn" }); applyFix({ "@hi18n/no-missing-translation-ids": "warn" }); applyFix({ "@hi18n/no-missing-translation-ids-in-types": "warn" }); if (current !== original) { if (options.checkOnly) { throw new Error(`Found diff in ${node_path_1.default.relative(projectPath, rewriteTargetFile)}`); } await node_fs_1.default.promises.writeFile(rewriteTargetFile, current, "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}`); } else if (message.severity >= 1) { console.warn(`Warning 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 ?? {}); matchers.push(["*", ["*"]]); 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(node_path_1.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_1.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 !== ".." && !node_path_1.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