intl-watcher
Version:
Automated translation key extraction and dictionary management plugin for Next.js
75 lines (71 loc) • 3.57 kB
JavaScript
import dedent from "dedent";
import pc from "picocolors";
import { ERROR, log, WARN } from "./logger.js";
import { getCommonPrefix } from "./utils.js";
const Severity = { Error: "Error", Warn: "Warn" };
function getDiagnosticConfig(severity) {
return severity === Severity.Error ? { label: ERROR, logger: log.error, formatter: pc.red } : { label: WARN, logger: log.warn, formatter: pc.yellow };
}
function generateHeader(sourceFile, targetLine, targetColumn) {
const projectFiles = sourceFile.getProject().getSourceFiles();
const rootDirectory = getCommonPrefix(projectFiles.map((file) => file.getFilePath()));
const filePath = sourceFile.getFilePath().substring(rootDirectory.length);
return `${filePath}:${targetLine}:${targetColumn} ${"\u2501".repeat(
Math.max(0, 100 - filePath.length - targetLine.toString().length - targetColumn.toString().length - 2)
)}`;
}
function buildSnippet(contextNode, targetNode) {
const sourceFile = contextNode.getSourceFile();
const { line: contextStartLine, column: contextStartColumn } = sourceFile.getLineAndColumnAtPos(
contextNode.getStart()
);
const { line: targetLine, column: targetColumn } = sourceFile.getLineAndColumnAtPos(targetNode.getStart());
const contextTextLines = contextNode.getText().split("\n");
const targetRelativeLineIndex = targetLine - contextStartLine;
const maxLineNumber = contextStartLine + contextTextLines.length - 1;
const lineNumberPadding = maxLineNumber.toString().length;
const snippetWithUnderline = contextTextLines.map((line, index) => {
const expandedLine = expandTabs(line);
const currentLineNumber = (contextStartLine + index).toString().padStart(lineNumberPadding);
if (index === targetRelativeLineIndex) {
const leadingText = targetLine === contextStartLine ? line.substring(contextStartColumn - 1, targetColumn - 1) : line.substring(0, targetColumn - 1);
const expandedLeadingText = expandTabs(leadingText);
const effectiveOffset = expandedLeadingText.length;
const targetText = targetNode.getText();
const firstLineOfTarget = targetText.split("\n")[0];
const expandedTargetFirstLine = expandTabs(firstLineOfTarget);
const underline = " ".repeat(effectiveOffset) + pc.red("^".repeat(expandedTargetFirstLine.length));
return `${currentLineNumber} | ${expandedLine}
${" ".repeat(lineNumberPadding)} | ${underline}`;
}
return `${currentLineNumber} | ${expandedLine}`;
}).join("\n");
return snippetWithUnderline.split("\n").map((line) => `${" ".repeat(8)}${line}`).join("\n");
}
function formatSuggestions(suggestions) {
return suggestions.join("\n").replace(/\n/g, `
${" ".repeat(10)}`);
}
function printDiagnostic(targetNode, contextNode, severity, detailedMessage, ...suggestions) {
const sourceFile = contextNode.getSourceFile();
const { line: targetLine, column: targetColumn } = sourceFile.getLineAndColumnAtPos(targetNode.getStart());
const header = generateHeader(sourceFile, targetLine, targetColumn);
const snippet = buildSnippet(contextNode, targetNode);
const formattedSuggestions = formatSuggestions(suggestions);
const { label, logger, formatter } = getDiagnosticConfig(severity);
const diagnosticMessage = dedent`
${header}
${label} ${formatter(detailedMessage)}
${snippet}
${pc.green(pc.bold("\u2139"))} ${pc.green(formattedSuggestions)}
`;
logger(diagnosticMessage);
log.info();
}
function expandTabs(line, tabSize = 2) {
return line.replace(/\t/g, " ".repeat(tabSize));
}
export {
Severity,
printDiagnostic
};