kystudio/build/explore-chinese.js (180 lines of code) (raw):

const fs = require('fs'); const path = require('path'); const npmPath = process.env.PWD; const configs = parseArgConfig([ '--rootPath', '--configFile', ]); let cacheResult = null; /* { "filePath": { [rowIdx]: { colIdx: 1, content: 'aaa 我是中文' } } } */ const chineseMap = {}; travellingFiles(resolvePath(configs.rootPath), (filePath) => { process.stdout.write(`Scan chinese in ${filePath}... `); if (!checkExcludeFile(filePath)) { const fileContent = removeFileComments(filePath); chineseMap[filePath] = findContentChinese(fileContent); } process.stdout.write(`Done.\n`); }); const hasChinese = Object.values(chineseMap).some(chineseResults => chineseResults.length); if (hasChinese) { for (const [filePath, chineseLines = []] of Object.entries(chineseMap)) { const isChineseFile = !!chineseLines.length; if (isChineseFile) { for (const chineseLine of chineseLines) { outputChineseLine({ ...chineseLine, filePath }); } } } throw new Error('Frontend code has chinese! Please check logs.'); } // Functions function parseArgConfig(argumentKeys = []) { let config = { rootPath: './src', }; // 读取arguments里面的配置 for (const key of argumentKeys) { if (typeof config[key.replace('--', '')] === 'boolean') { const keyIndex = process.argv.findIndex(arg => arg === key); config = { ...config, [key.replace('--', '')]: keyIndex !== -1 }; } else { const keyIndex = process.argv.findIndex(arg => arg === key); const value = process.argv[keyIndex + 1]; if (value && keyIndex !== -1) { config = { ...config, [key.replace('--', '')]: value }; } } } // 如果arguments里面有配置文件,则用文件的配置覆盖arguments里面的配置 if (config.configFile) { const fileConfig = readConfigFile(config.configFile); config = { ...config, ...fileConfig }; } return config; } function resolvePath(filePath) { return path.resolve(npmPath, filePath); } function readConfigFile(filePath) { return require(resolvePath(filePath)) } function travellingFiles(filePath, callback) { const stat = fs.statSync(filePath); if (stat.isDirectory()) { const children = fs.readdirSync(filePath); for (const child of children) { travellingFiles(path.resolve(filePath, child), callback); } } else if (stat.isFile()) { callback(filePath); } } function removeFileComments(filePath) { switch (path.extname(filePath)) { case '.vue': return removeVueComments(filePath); case '.js': case '.css': case '.less': return removeJSComments(filePath); default: return undefined; } } function removeVueComments(filePath) { let content = fs.readFileSync(filePath, 'utf-8'); content = content.replace(/<!--\s*[\w\W\r\n]*?\s*-->/gmi, ''); content = removeHTMLComments(content, /<template\b[^>]*>([^]*)<\/template>/im); content = removeScriptComments(content, /<script\b[^>]*>([^]*)<\/script>/im); content = removeScriptComments(content, /<style\b[^>]*>([^]*)<\/style>/im); return content; } function removeJSComments(filePath) { let content = fs.readFileSync(filePath, 'utf-8'); content = removeScriptComments(content); return content; } function removeHTMLComments(content = '', regex) { let cleanedContent = content; if (regex) { // 获取HTML内容 const htmlContent = content.match(regex) && content.match(regex)[0]; if (htmlContent) { const cleanedHTMLContent = htmlContent // 去掉 <!--注释--> .replace(/<!--\s*[\w\W\r\n]*?\s*-->/gmi, ''); cleanedContent = content.replace(htmlContent, cleanedHTMLContent); } } else { const cleanedHTMLContent = content // 去掉 <!--注释--> .replace(/<!--\s*[\w\W\r\n]*?\s*-->/gmi, ''); cleanedContent = content.replace(content, cleanedHTMLContent); } return cleanedContent; } function removeScriptComments(content = '', regex) { let cleanedContent = content; if (regex) { // 获取script内容 const scriptContent = content.match(regex) && content.match(regex)[0]; if (scriptContent) { const cleanedScriptContent = removeLocales(scriptContent) // 去掉 /* 注释 */ .replace(/\/\*[\w\W\r\n]*?\*\//gmi, '') // 去掉 // 注释 TODO: 字符串里面带双斜杠会被干掉,这个有待研究 .replace(/\/\/[\w\W]*?\n/gmi, '\n'); cleanedContent = content.replace(scriptContent, cleanedScriptContent); } } else { const cleanedScriptContent = removeLocales(content) // 去掉 /* 注释 */ .replace(/\/\*[\w\W\r\n]*?\*\//gmi, '') // 去掉 // 注释 TODO: 字符串里面带双斜杠会被干掉,这个有待研究 .replace(/\/\/[\w\W]*?\n/gmi, '\n'); cleanedContent = content.replace(content, cleanedScriptContent); } return cleanedContent; } function findContentChinese(fileContent = '') { const results = []; const textLines = fileContent.split('\n'); textLines.forEach((text, rowIdx) => { const result = text.match(/[\u4e00-\u9fa5]/); if (result) { const { index: colIdx } = result; results.push({ rowIdx, colIdx, text }); } }); return results; } function cutToLastChar(string = '', char = '') { const lastIndex = string.lastIndexOf(char) + 1; return { text: lastIndex !== -1 ? string.slice(0, lastIndex) : '', lastIndex }; } function removeLocales(content = '') { let resultString = content; const localesResult = content.match(/locales:\s*{/); if (localesResult) { const startIndex = localesResult.index; const lastIndex = startIndex + + localesResult[0].length + findLocalesLastIndex(`{${content.split(/locales:\s*{/)[1]}`); resultString = content.replace(content.slice(startIndex, lastIndex), ''); } return resultString; } function findLocalesLastIndex(string = '', lastIndex) { try { eval(`cacheResult = ${string};`); return lastIndex; } catch { const removedResult = cutToLastChar(string.slice(0, string.length - 1), '}'); return removedResult.text ? findLocalesLastIndex(removedResult.text, removedResult.lastIndex) : null; } } function checkExcludeFile(filePath = '') { return configs.excludes && configs.excludes.some(exclude => exclude.test(filePath)); } function outputChineseLine({ rowIdx, colIdx, text, filePath }) { console.log(`Line ${rowIdx}:${colIdx}: Find chinese in file ${filePath}`); const perfixInfo = `> ${rowIdx} | `; console.log(`${perfixInfo}${text}`); for (let i = 0; i < perfixInfo.length + colIdx; i += 1) { process.stdout.write(' '); } console.log('^'); console.log('\r\n'); }