feat: 支持检查i18n中key包含的占位符
This commit is contained in:
parent
bafe3e0c36
commit
6f49d59ffd
|
|
@ -1572,37 +1572,55 @@ const loadI18nData = (dirPath) => {
|
|||
return null;
|
||||
};
|
||||
/**
|
||||
* 检查 key 是否存在于 i18n 数据中
|
||||
* @param i18nData i18n 数据对象
|
||||
* @param key i18n key,支持点号分隔的嵌套路径
|
||||
* @returns boolean
|
||||
* 从 i18n 值中提取占位符
|
||||
* 支持格式:%{key} 或 {{key}}
|
||||
*/
|
||||
const extractPlaceholders = (value) => {
|
||||
const placeholders = [];
|
||||
// 匹配 %{xxx} 格式
|
||||
const percentPattern = /%\{([^}]+)\}/g;
|
||||
let match;
|
||||
while ((match = percentPattern.exec(value)) !== null) {
|
||||
placeholders.push(match[1]);
|
||||
}
|
||||
// 匹配 {{xxx}} 格式(如果需要)
|
||||
const bracePattern = /\{\{([^}]+)\}\}/g;
|
||||
while ((match = bracePattern.exec(value)) !== null) {
|
||||
if (!placeholders.includes(match[1])) {
|
||||
placeholders.push(match[1]);
|
||||
}
|
||||
}
|
||||
return placeholders;
|
||||
};
|
||||
const isKeyExistsInI18nData = (i18nData, key) => {
|
||||
if (!i18nData) {
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
}
|
||||
// 辅助递归函数
|
||||
const checkPath = (obj, remainingKey) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
}
|
||||
// 如果剩余key完整存在于当前对象,直接返回true
|
||||
// 如果剩余key完整存在于当前对象
|
||||
if (obj.hasOwnProperty(remainingKey)) {
|
||||
return true;
|
||||
const value = obj[remainingKey];
|
||||
return {
|
||||
exists: true,
|
||||
value: typeof value === 'string' ? value : undefined,
|
||||
placeholders: typeof value === 'string' ? extractPlaceholders(value) : []
|
||||
};
|
||||
}
|
||||
// 尝试所有可能的分割点
|
||||
for (let i = 1; i <= remainingKey.length; i++) {
|
||||
const firstPart = remainingKey.substring(0, i);
|
||||
const restPart = remainingKey.substring(i + 1); // +1 跳过点号
|
||||
// 如果当前部分存在且后面还有内容(有点号分隔)
|
||||
const restPart = remainingKey.substring(i + 1);
|
||||
if (obj.hasOwnProperty(firstPart) && i < remainingKey.length && remainingKey[i] === '.') {
|
||||
// 递归检查剩余部分
|
||||
if (checkPath(obj[firstPart], restPart)) {
|
||||
return true;
|
||||
const result = checkPath(obj[firstPart], restPart);
|
||||
if (result.exists) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
};
|
||||
return checkPath(i18nData, key);
|
||||
};
|
||||
|
|
@ -1853,8 +1871,13 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
const missingKeys = [];
|
||||
const foundKeys = [];
|
||||
for (const key of keyVariants) {
|
||||
const exists = isKeyExistsInI18nData(i18nData, key) ||
|
||||
checkWithCommonStringExists(i18nData, key);
|
||||
const result = isKeyExistsInI18nData(i18nData, key);
|
||||
const commonResult = result.exists ? result : checkWithCommonStringExists(i18nData, key);
|
||||
const exists = commonResult.exists;
|
||||
// 如果找到了 key,检查占位符
|
||||
if (exists && commonResult.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, commonResult.placeholders, addDiagnostic);
|
||||
}
|
||||
if (exists) {
|
||||
foundKeys.push(key);
|
||||
}
|
||||
|
|
@ -1935,7 +1958,93 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
else {
|
||||
return isKeyExistsInI18nData(i18nData, key);
|
||||
}
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
};
|
||||
/**
|
||||
* 检查 t 函数的第二个参数是否提供了所需的占位符
|
||||
*/
|
||||
const checkSecondArgument = (callNode, placeholders, addDiagnostic) => {
|
||||
if (placeholders.length === 0) {
|
||||
return; // 没有占位符,不需要检查
|
||||
}
|
||||
const args = callNode.arguments;
|
||||
if (args.length < 2) {
|
||||
// 缺少第二个参数
|
||||
addDiagnostic(callNode, `i18n 值包含占位符 ${placeholders.map(p => `%{${p}}`).join(', ')},但未提供第二个参数。`, 9210, {
|
||||
reason: 'MissingSecondArgument',
|
||||
reasonDetails: [
|
||||
`✘ 需要的占位符: ${placeholders.join(', ')}`,
|
||||
'建议: 添加第二个参数对象,如: t("key", { url: "..." })'
|
||||
]
|
||||
});
|
||||
return;
|
||||
}
|
||||
const secondArg = args[1];
|
||||
// 检查第二个参数是否是对象字面量
|
||||
if (!ts.isObjectLiteralExpression(secondArg)) {
|
||||
addDiagnostic(callNode, `i18n 值包含占位符,但第二个参数不是字面对象,无法检查占位符是否提供。`, 9211, {
|
||||
reason: 'NonLiteralSecondArgument',
|
||||
reasonDetails: [
|
||||
`✘ 第二个参数类型: ${secondArg.getText()}`,
|
||||
`需要的占位符: ${placeholders.join(', ')}`,
|
||||
'建议: 使用对象字面量,如: { url: "..." }'
|
||||
]
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 提取对象字面量中的属性名
|
||||
const providedKeys = new Set();
|
||||
secondArg.properties.forEach(prop => {
|
||||
if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {
|
||||
if (ts.isIdentifier(prop.name)) {
|
||||
providedKeys.add(prop.name.text);
|
||||
}
|
||||
else if (ts.isStringLiteral(prop.name)) {
|
||||
providedKeys.add(prop.name.text);
|
||||
}
|
||||
else if (ts.isComputedPropertyName(prop.name)) {
|
||||
// 计算属性名,无法静态检查
|
||||
// 可以选择跳过或警告
|
||||
}
|
||||
}
|
||||
else if (ts.isSpreadAssignment(prop)) {
|
||||
// 展开运算符,无法静态检查所有属性
|
||||
// 可以选择跳过检查
|
||||
return;
|
||||
}
|
||||
});
|
||||
// 检查是否有展开运算符
|
||||
const hasSpread = secondArg.properties.some(prop => ts.isSpreadAssignment(prop));
|
||||
// 检查缺失的占位符
|
||||
const missingPlaceholders = placeholders.filter(p => !providedKeys.has(p));
|
||||
if (missingPlaceholders.length > 0) {
|
||||
const details = [];
|
||||
if (hasSpread) {
|
||||
details.push('¡ 第二个参数包含展开运算符,可能提供了额外的属性');
|
||||
}
|
||||
details.push('需要的占位符:');
|
||||
placeholders.forEach(p => {
|
||||
if (providedKeys.has(p)) {
|
||||
details.push(` ✓ ${p} - 已提供`);
|
||||
}
|
||||
else {
|
||||
details.push(` ✘ ${p} - 缺失`);
|
||||
}
|
||||
});
|
||||
if (!hasSpread) {
|
||||
addDiagnostic(callNode, `i18n 第二个参数缺少占位符: ${missingPlaceholders.join(', ')}`, 9212, {
|
||||
reason: 'MissingPlaceholders',
|
||||
reasonDetails: details
|
||||
});
|
||||
}
|
||||
else {
|
||||
// 有展开运算符,降级为警告
|
||||
addDiagnostic(callNode, `i18n 第二个参数可能缺少占位符: ${missingPlaceholders.join(', ')} (包含展开运算符,无法完全确定)`, 9213, {
|
||||
reason: 'PossiblyMissingPlaceholders',
|
||||
reasonDetails: details
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 检查变量引用的 i18n key
|
||||
|
|
@ -1949,8 +2058,19 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
const missingKeys = [];
|
||||
const foundKeys = [];
|
||||
for (const value of analysis.literalValues) {
|
||||
const exists = isKeyExistsInI18nData(i18nData, value) ||
|
||||
checkWithCommonStringExists(i18nData, value);
|
||||
const result = isKeyExistsInI18nData(i18nData, value);
|
||||
const commonResult = result.exists ? result : checkWithCommonStringExists(i18nData, value);
|
||||
const exists = commonResult.exists;
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
// 检查占位符(对于字面量联合类型,只在所有值都找到时检查)
|
||||
if (commonResult.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, commonResult.placeholders, addDiagnostic);
|
||||
}
|
||||
}
|
||||
else {
|
||||
missingKeys.push(value);
|
||||
}
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
}
|
||||
|
|
@ -2009,15 +2129,20 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
}
|
||||
};
|
||||
const checkWithCommonString = (i18nData, key, callNode, localePath) => {
|
||||
let result;
|
||||
if (commonLocaleKeyRegex.test(key)) {
|
||||
// 公共字符串,格式如 common::title, 这里把namespace提取出来
|
||||
const parts = commonLocaleKeyRegex.exec(key);
|
||||
if (parts && parts.length >= 2) {
|
||||
const namespace = parts[1];
|
||||
const localeData = getCommonLocaleData(namespace);
|
||||
const actualKey = key.substring(namespace.length + 2); // 去掉 namespace 和 ::
|
||||
if (isKeyExistsInI18nData(localeData, actualKey)) {
|
||||
return; // 找到,直接返回
|
||||
const actualKey = key.substring(namespace.length + 2);
|
||||
result = isKeyExistsInI18nData(localeData, actualKey);
|
||||
if (result.exists) {
|
||||
// 检查占位符
|
||||
if (result.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, result.placeholders, addDiagnostic);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
addDiagnostic(callNode, `i18n key "${key}" not found in public locale files: namespace "${namespace}".`, 9200);
|
||||
|
|
@ -2035,14 +2160,18 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
}
|
||||
}
|
||||
else if (entityLocaleKeyRegex.test(key)) {
|
||||
// 实体字符串,格式如 entity:attr.name
|
||||
const parts = entityLocaleKeyRegex.exec(key);
|
||||
if (parts && parts.length >= 2) {
|
||||
const entity = parts[1];
|
||||
const localeData = getEntityLocaleData(entity);
|
||||
const actualKey = key.substring(entity.length + 1);
|
||||
if (isKeyExistsInI18nData(localeData, actualKey)) {
|
||||
return; // 找到,直接返回
|
||||
result = isKeyExistsInI18nData(localeData, actualKey);
|
||||
if (result.exists) {
|
||||
// 检查占位符
|
||||
if (result.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, result.placeholders, addDiagnostic);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
addDiagnostic(callNode, `i18n key "${key}" not found in entity locale files: entity "${entity}".`, 9200);
|
||||
|
|
@ -2060,9 +2189,13 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
}
|
||||
}
|
||||
else {
|
||||
// 普通字符串,直接检查
|
||||
if (isKeyExistsInI18nData(i18nData, key)) {
|
||||
return; // 找到,直接返回
|
||||
result = isKeyExistsInI18nData(i18nData, key);
|
||||
if (result.exists) {
|
||||
// 检查占位符
|
||||
if (result.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, result.placeholders, addDiagnostic);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
addDiagnostic(callNode, `i18n key "${key}" not found in its locale files: ${localePath}.`, 9200);
|
||||
|
|
@ -2146,8 +2279,19 @@ const checkI18nKeys = (pwd, callSet, program, typeChecker) => {
|
|||
const missingKeys = [];
|
||||
const foundKeys = [];
|
||||
for (const value of analysis.literalValues) {
|
||||
const exists = isKeyExistsInI18nData(i18nData, value) ||
|
||||
checkWithCommonStringExists(i18nData, value);
|
||||
const result = isKeyExistsInI18nData(i18nData, value);
|
||||
const commonResult = result.exists ? result : checkWithCommonStringExists(i18nData, value);
|
||||
const exists = commonResult.exists;
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
// 检查占位符(对于字面量联合类型,只在所有值都找到时检查)
|
||||
if (commonResult.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, commonResult.placeholders, addDiagnostic);
|
||||
}
|
||||
}
|
||||
else {
|
||||
missingKeys.push(value);
|
||||
}
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1930,47 +1930,75 @@ const loadI18nData = (dirPath: string): Record<string, string> | null => {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 key 是否存在于 i18n 数据中
|
||||
* @param i18nData i18n 数据对象
|
||||
* @param key i18n key,支持点号分隔的嵌套路径
|
||||
* @returns boolean
|
||||
interface I18nKeyCheckResult {
|
||||
exists: boolean;
|
||||
value?: string;
|
||||
placeholders: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 i18n 值中提取占位符
|
||||
* 支持格式:%{key} 或 {{key}}
|
||||
*/
|
||||
const isKeyExistsInI18nData = (i18nData: Record<string, string> | null, key: string): boolean => {
|
||||
if (!i18nData) {
|
||||
return false;
|
||||
const extractPlaceholders = (value: string): string[] => {
|
||||
const placeholders: string[] = [];
|
||||
|
||||
// 匹配 %{xxx} 格式
|
||||
const percentPattern = /%\{([^}]+)\}/g;
|
||||
let match;
|
||||
while ((match = percentPattern.exec(value)) !== null) {
|
||||
placeholders.push(match[1]);
|
||||
}
|
||||
|
||||
// 辅助递归函数
|
||||
const checkPath = (obj: any, remainingKey: string): boolean => {
|
||||
// 匹配 {{xxx}} 格式(如果需要)
|
||||
const bracePattern = /\{\{([^}]+)\}\}/g;
|
||||
while ((match = bracePattern.exec(value)) !== null) {
|
||||
if (!placeholders.includes(match[1])) {
|
||||
placeholders.push(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return placeholders;
|
||||
};
|
||||
|
||||
const isKeyExistsInI18nData = (i18nData: Record<string, string> | null, key: string): I18nKeyCheckResult => {
|
||||
if (!i18nData) {
|
||||
return { exists: false, placeholders: [] };
|
||||
}
|
||||
|
||||
const checkPath = (obj: any, remainingKey: string): I18nKeyCheckResult => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
}
|
||||
|
||||
// 如果剩余key完整存在于当前对象,直接返回true
|
||||
// 如果剩余key完整存在于当前对象
|
||||
if (obj.hasOwnProperty(remainingKey)) {
|
||||
return true;
|
||||
const value = obj[remainingKey];
|
||||
return {
|
||||
exists: true,
|
||||
value: typeof value === 'string' ? value : undefined,
|
||||
placeholders: typeof value === 'string' ? extractPlaceholders(value) : []
|
||||
};
|
||||
}
|
||||
|
||||
// 尝试所有可能的分割点
|
||||
for (let i = 1; i <= remainingKey.length; i++) {
|
||||
const firstPart = remainingKey.substring(0, i);
|
||||
const restPart = remainingKey.substring(i + 1); // +1 跳过点号
|
||||
const restPart = remainingKey.substring(i + 1);
|
||||
|
||||
// 如果当前部分存在且后面还有内容(有点号分隔)
|
||||
if (obj.hasOwnProperty(firstPart) && i < remainingKey.length && remainingKey[i] === '.') {
|
||||
// 递归检查剩余部分
|
||||
if (checkPath(obj[firstPart], restPart)) {
|
||||
return true;
|
||||
const result = checkPath(obj[firstPart], restPart);
|
||||
if (result.exists) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
};
|
||||
|
||||
return checkPath(i18nData, key);
|
||||
}
|
||||
};
|
||||
|
||||
// 表达式类型分析结果
|
||||
interface TypeAnalysis {
|
||||
|
|
@ -2282,8 +2310,14 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
const foundKeys: string[] = [];
|
||||
|
||||
for (const key of keyVariants) {
|
||||
const exists = isKeyExistsInI18nData(i18nData, key) ||
|
||||
checkWithCommonStringExists(i18nData, key);
|
||||
const result = isKeyExistsInI18nData(i18nData, key);
|
||||
const commonResult = result.exists ? result : checkWithCommonStringExists(i18nData, key);
|
||||
const exists = commonResult.exists;
|
||||
|
||||
// 如果找到了 key,检查占位符
|
||||
if (exists && commonResult.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, commonResult.placeholders, addDiagnostic);
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
foundKeys.push(key);
|
||||
|
|
@ -2360,7 +2394,7 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
/**
|
||||
* 检查是否存在(不添加诊断,仅返回结果)
|
||||
*/
|
||||
const checkWithCommonStringExists = (i18nData: Record<string, string>, key: string): boolean => {
|
||||
const checkWithCommonStringExists = (i18nData: Record<string, string>, key: string): I18nKeyCheckResult => {
|
||||
if (commonLocaleKeyRegex.test(key)) {
|
||||
const parts = commonLocaleKeyRegex.exec(key);
|
||||
if (parts && parts.length >= 2) {
|
||||
|
|
@ -2381,7 +2415,123 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
return isKeyExistsInI18nData(i18nData, key);
|
||||
}
|
||||
|
||||
return false;
|
||||
return { exists: false, placeholders: [] };
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查 t 函数的第二个参数是否提供了所需的占位符
|
||||
*/
|
||||
const checkSecondArgument = (
|
||||
callNode: ts.CallExpression,
|
||||
placeholders: string[],
|
||||
addDiagnostic: (node: ts.Node, message: string, code: number, options?: any) => void
|
||||
): void => {
|
||||
if (placeholders.length === 0) {
|
||||
return; // 没有占位符,不需要检查
|
||||
}
|
||||
|
||||
const args = callNode.arguments;
|
||||
if (args.length < 2) {
|
||||
// 缺少第二个参数
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
`i18n 值包含占位符 ${placeholders.map(p => `%{${p}}`).join(', ')},但未提供第二个参数。`,
|
||||
9210,
|
||||
{
|
||||
reason: 'MissingSecondArgument',
|
||||
reasonDetails: [
|
||||
`✘ 需要的占位符: ${placeholders.join(', ')}`,
|
||||
'建议: 添加第二个参数对象,如: t("key", { url: "..." })'
|
||||
]
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const secondArg = args[1];
|
||||
|
||||
// 检查第二个参数是否是对象字面量
|
||||
if (!ts.isObjectLiteralExpression(secondArg)) {
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
`i18n 值包含占位符,但第二个参数不是字面对象,无法检查占位符是否提供。`,
|
||||
9211,
|
||||
{
|
||||
reason: 'NonLiteralSecondArgument',
|
||||
reasonDetails: [
|
||||
`✘ 第二个参数类型: ${secondArg.getText()}`,
|
||||
`需要的占位符: ${placeholders.join(', ')}`,
|
||||
'建议: 使用对象字面量,如: { url: "..." }'
|
||||
]
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 提取对象字面量中的属性名
|
||||
const providedKeys = new Set<string>();
|
||||
secondArg.properties.forEach(prop => {
|
||||
if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {
|
||||
if (ts.isIdentifier(prop.name)) {
|
||||
providedKeys.add(prop.name.text);
|
||||
} else if (ts.isStringLiteral(prop.name)) {
|
||||
providedKeys.add(prop.name.text);
|
||||
} else if (ts.isComputedPropertyName(prop.name)) {
|
||||
// 计算属性名,无法静态检查
|
||||
// 可以选择跳过或警告
|
||||
}
|
||||
} else if (ts.isSpreadAssignment(prop)) {
|
||||
// 展开运算符,无法静态检查所有属性
|
||||
// 可以选择跳过检查
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// 检查是否有展开运算符
|
||||
const hasSpread = secondArg.properties.some(prop => ts.isSpreadAssignment(prop));
|
||||
|
||||
// 检查缺失的占位符
|
||||
const missingPlaceholders = placeholders.filter(p => !providedKeys.has(p));
|
||||
|
||||
if (missingPlaceholders.length > 0) {
|
||||
const details: string[] = [];
|
||||
|
||||
if (hasSpread) {
|
||||
details.push('¡ 第二个参数包含展开运算符,可能提供了额外的属性');
|
||||
}
|
||||
|
||||
details.push('需要的占位符:');
|
||||
placeholders.forEach(p => {
|
||||
if (providedKeys.has(p)) {
|
||||
details.push(` ✓ ${p} - 已提供`);
|
||||
} else {
|
||||
details.push(` ✘ ${p} - 缺失`);
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasSpread) {
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
`i18n 第二个参数缺少占位符: ${missingPlaceholders.join(', ')}`,
|
||||
9212,
|
||||
{
|
||||
reason: 'MissingPlaceholders',
|
||||
reasonDetails: details
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 有展开运算符,降级为警告
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
`i18n 第二个参数可能缺少占位符: ${missingPlaceholders.join(', ')} (包含展开运算符,无法完全确定)`,
|
||||
9213,
|
||||
{
|
||||
reason: 'PossiblyMissingPlaceholders',
|
||||
reasonDetails: details
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2406,8 +2556,19 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
const foundKeys: string[] = [];
|
||||
|
||||
for (const value of analysis.literalValues) {
|
||||
const exists = isKeyExistsInI18nData(i18nData, value) ||
|
||||
checkWithCommonStringExists(i18nData, value);
|
||||
const result = isKeyExistsInI18nData(i18nData, value);
|
||||
const commonResult = result.exists ? result : checkWithCommonStringExists(i18nData, value);
|
||||
const exists = commonResult.exists;
|
||||
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
// 检查占位符(对于字面量联合类型,只在所有值都找到时检查)
|
||||
if (commonResult.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, commonResult.placeholders, addDiagnostic);
|
||||
}
|
||||
} else {
|
||||
missingKeys.push(value);
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
|
|
@ -2485,16 +2646,28 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
}
|
||||
};
|
||||
|
||||
const checkWithCommonString = (i18nData: Record<string, string>, key: string, callNode: ts.CallExpression, localePath: string) => {
|
||||
const checkWithCommonString = (
|
||||
i18nData: Record<string, string>,
|
||||
key: string,
|
||||
callNode: ts.CallExpression,
|
||||
localePath: string
|
||||
) => {
|
||||
let result: I18nKeyCheckResult;
|
||||
|
||||
if (commonLocaleKeyRegex.test(key)) {
|
||||
// 公共字符串,格式如 common::title, 这里把namespace提取出来
|
||||
const parts = commonLocaleKeyRegex.exec(key);
|
||||
if (parts && parts.length >= 2) {
|
||||
const namespace = parts[1];
|
||||
const localeData = getCommonLocaleData(namespace);
|
||||
const actualKey = key.substring(namespace.length + 2); // 去掉 namespace 和 ::
|
||||
if (isKeyExistsInI18nData(localeData, actualKey)) {
|
||||
return; // 找到,直接返回
|
||||
const actualKey = key.substring(namespace.length + 2);
|
||||
result = isKeyExistsInI18nData(localeData, actualKey);
|
||||
|
||||
if (result.exists) {
|
||||
// 检查占位符
|
||||
if (result.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, result.placeholders, addDiagnostic);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
|
|
@ -2518,14 +2691,19 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
return;
|
||||
}
|
||||
} else if (entityLocaleKeyRegex.test(key)) {
|
||||
// 实体字符串,格式如 entity:attr.name
|
||||
const parts = entityLocaleKeyRegex.exec(key);
|
||||
if (parts && parts.length >= 2) {
|
||||
const entity = parts[1];
|
||||
const localeData = getEntityLocaleData(entity);
|
||||
const actualKey = key.substring(entity.length + 1);
|
||||
if (isKeyExistsInI18nData(localeData, actualKey)) {
|
||||
return; // 找到,直接返回
|
||||
result = isKeyExistsInI18nData(localeData, actualKey);
|
||||
|
||||
if (result.exists) {
|
||||
// 检查占位符
|
||||
if (result.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, result.placeholders, addDiagnostic);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
|
|
@ -2549,9 +2727,14 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
// 普通字符串,直接检查
|
||||
if (isKeyExistsInI18nData(i18nData, key)) {
|
||||
return; // 找到,直接返回
|
||||
result = isKeyExistsInI18nData(i18nData, key);
|
||||
|
||||
if (result.exists) {
|
||||
// 检查占位符
|
||||
if (result.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, result.placeholders, addDiagnostic);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
addDiagnostic(
|
||||
callNode,
|
||||
|
|
@ -2561,7 +2744,7 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 逐文件处理
|
||||
groupedCalls.forEach((calls, filePath) => {
|
||||
|
|
@ -2656,8 +2839,19 @@ const checkI18nKeys = (pwd: string, callSet: Set<ts.CallExpression>, program: ts
|
|||
const foundKeys: string[] = [];
|
||||
|
||||
for (const value of analysis.literalValues) {
|
||||
const exists = isKeyExistsInI18nData(i18nData, value) ||
|
||||
checkWithCommonStringExists(i18nData, value);
|
||||
const result = isKeyExistsInI18nData(i18nData, value);
|
||||
const commonResult = result.exists ? result : checkWithCommonStringExists(i18nData, value);
|
||||
const exists = commonResult.exists;
|
||||
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
// 检查占位符(对于字面量联合类型,只在所有值都找到时检查)
|
||||
if (commonResult.placeholders.length > 0) {
|
||||
checkSecondArgument(callNode, commonResult.placeholders, addDiagnostic);
|
||||
}
|
||||
} else {
|
||||
missingKeys.push(value);
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
foundKeys.push(value);
|
||||
|
|
|
|||
Loading…
Reference in New Issue