From 7613df14e60f5f55fdd40d553cddae644e20664c Mon Sep 17 00:00:00 2001 From: qcqcqc <1220204124@zust.edu.cn> Date: Tue, 6 Jan 2026 17:49:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/compiler/tscBuilder.js | 110 +++++++++++++++++++++++++++---- src/compiler/tscBuilder.ts | 131 +++++++++++++++++++++++++++++++++---- 2 files changed, 217 insertions(+), 24 deletions(-) diff --git a/lib/compiler/tscBuilder.js b/lib/compiler/tscBuilder.js index f5c14a1..5c9ef97 100644 --- a/lib/compiler/tscBuilder.js +++ b/lib/compiler/tscBuilder.js @@ -557,17 +557,95 @@ function performCustomChecks(pwd, program, typeChecker, customConfig) { }; const preprocessIgnoreComments = (sourceFile) => { const fullText = sourceFile.getFullText(); + // 收集文件中所有的注释及其位置 + const allComments = []; + const scanComments = (pos, end) => { + const leading = ts.getLeadingCommentRanges(fullText, pos); + const trailing = ts.getTrailingCommentRanges(fullText, end); + if (leading) { + leading.forEach(comment => { + const text = fullText.substring(comment.pos, comment.end); + allComments.push({ + pos: comment.pos, + end: comment.end, + text, + isIgnore: isIgnoreComment(text) + }); + }); + } + if (trailing) { + trailing.forEach(comment => { + const text = fullText.substring(comment.pos, comment.end); + allComments.push({ + pos: comment.pos, + end: comment.end, + text, + isIgnore: isIgnoreComment(text) + }); + }); + } + }; + // 扫描整个文件的注释 + const scanNode = (node) => { + scanComments(node.getFullStart(), node.getEnd()); + ts.forEachChild(node, scanNode); + }; + scanNode(sourceFile); + // 检查节点是否在忽略注释的影响范围内 + const isNodeCoveredByIgnoreComment = (node) => { + const nodeStart = node.getStart(sourceFile); + const nodeEnd = node.getEnd(); + for (const comment of allComments) { + if (!comment.isIgnore) + continue; + // 情况1:注释在节点之前(前导注释) + // 允许注释和节点之间有少量空白(最多50个字符) + if (comment.end <= nodeStart && nodeStart - comment.end <= 50) { + return true; + } + // 情况2:注释在节点之后(尾随注释) + // 允许节点和注释之间有少量空白(最多50个字符) + if (comment.pos >= nodeEnd && comment.pos - nodeEnd <= 50) { + return true; + } + // 情况3:注释在节点内部(JSX 表达式中的注释) + if (comment.pos >= nodeStart && comment.end <= nodeEnd) { + return true; + } + } + return false; + }; const visit = (node) => { - const nodeFullStart = node.getFullStart(); - const leadingComments = ts.getLeadingCommentRanges(fullText, nodeFullStart); - if (leadingComments) { - for (const comment of leadingComments) { - const commentText = fullText.substring(comment.pos, comment.end); - if (isIgnoreComment(commentText)) { - // 标记当前节点及其子节点 - markNodeAndChildren(node); - return; // 不需要继续遍历子节点 - } + // 检查当前节点是否被忽略注释覆盖 + if (isNodeCoveredByIgnoreComment(node)) { + markNodeAndChildren(node); + return; + } + // 特殊处理:JSX 表达式 + if (ts.isJsxExpression(node)) { + // 检查 JSX 表达式内部是否有忽略注释 + if (node.expression && isNodeCoveredByIgnoreComment(node.expression)) { + markNodeAndChildren(node.expression); + } + } + // 特殊处理:条件表达式(三元运算符) + if (ts.isConditionalExpression(node)) { + // 检查 whenTrue 和 whenFalse 分支 + if (isNodeCoveredByIgnoreComment(node.whenTrue)) { + markNodeAndChildren(node.whenTrue); + } + if (isNodeCoveredByIgnoreComment(node.whenFalse)) { + markNodeAndChildren(node.whenFalse); + } + } + // 特殊处理:二元表达式 + if (ts.isBinaryExpression(node)) { + // 检查左右操作数 + if (isNodeCoveredByIgnoreComment(node.left)) { + markNodeAndChildren(node.left); + } + if (isNodeCoveredByIgnoreComment(node.right)) { + markNodeAndChildren(node.right); } } ts.forEachChild(node, visit); @@ -584,6 +662,16 @@ function performCustomChecks(pwd, program, typeChecker, customConfig) { if (ignoreCommentNodes.has(node)) { return true; } + // 如果文件开头有忽略注释,整个文件都忽略 + const fileStartComments = ts.getLeadingCommentRanges(sourceFile.getFullText(), 0); + if (fileStartComments) { + for (const comment of fileStartComments) { + const commentText = sourceFile.getFullText().substring(comment.pos, comment.end); + if (isIgnoreComment(commentText)) { + return true; + } + } + } // 向上查找父节点(最多3层) let current = node.parent; let depth = 0; @@ -2286,7 +2374,7 @@ function performCustomChecks(pwd, program, typeChecker, customConfig) { } else { // 普通格式的 key,但本地没有 i18n 文件 - addDiagnostic(callNode, `i18n key "${key}" 无法检查,因为未找到本地化文件: ${localePath}。`, 9202); + addDiagnostic(callNode, `i18n key "${key}" 无法检查,因为找不到locales文件: ${localePath}。`, 9202); } } else if (ts.isTemplateExpression(firstArg) || diff --git a/src/compiler/tscBuilder.ts b/src/compiler/tscBuilder.ts index 002b242..7500b24 100644 --- a/src/compiler/tscBuilder.ts +++ b/src/compiler/tscBuilder.ts @@ -727,18 +727,108 @@ export function performCustomChecks( const preprocessIgnoreComments = (sourceFile: ts.SourceFile): void => { const fullText = sourceFile.getFullText(); - const visit = (node: ts.Node): void => { - const nodeFullStart = node.getFullStart(); - const leadingComments = ts.getLeadingCommentRanges(fullText, nodeFullStart); + // 收集文件中所有的注释及其位置 + const allComments: Array<{ pos: number; end: number; text: string; isIgnore: boolean }> = []; - if (leadingComments) { - for (const comment of leadingComments) { - const commentText = fullText.substring(comment.pos, comment.end); - if (isIgnoreComment(commentText)) { - // 标记当前节点及其子节点 - markNodeAndChildren(node); - return; // 不需要继续遍历子节点 - } + const scanComments = (pos: number, end: number) => { + const leading = ts.getLeadingCommentRanges(fullText, pos); + const trailing = ts.getTrailingCommentRanges(fullText, end); + + if (leading) { + leading.forEach(comment => { + const text = fullText.substring(comment.pos, comment.end); + allComments.push({ + pos: comment.pos, + end: comment.end, + text, + isIgnore: isIgnoreComment(text) + }); + }); + } + + if (trailing) { + trailing.forEach(comment => { + const text = fullText.substring(comment.pos, comment.end); + allComments.push({ + pos: comment.pos, + end: comment.end, + text, + isIgnore: isIgnoreComment(text) + }); + }); + } + }; + + // 扫描整个文件的注释 + const scanNode = (node: ts.Node): void => { + scanComments(node.getFullStart(), node.getEnd()); + ts.forEachChild(node, scanNode); + }; + scanNode(sourceFile); + + // 检查节点是否在忽略注释的影响范围内 + const isNodeCoveredByIgnoreComment = (node: ts.Node): boolean => { + const nodeStart = node.getStart(sourceFile); + const nodeEnd = node.getEnd(); + + for (const comment of allComments) { + if (!comment.isIgnore) continue; + + // 情况1:注释在节点之前(前导注释) + // 允许注释和节点之间有少量空白(最多50个字符) + if (comment.end <= nodeStart && nodeStart - comment.end <= 50) { + return true; + } + + // 情况2:注释在节点之后(尾随注释) + // 允许节点和注释之间有少量空白(最多50个字符) + if (comment.pos >= nodeEnd && comment.pos - nodeEnd <= 50) { + return true; + } + + // 情况3:注释在节点内部(JSX 表达式中的注释) + if (comment.pos >= nodeStart && comment.end <= nodeEnd) { + return true; + } + } + + return false; + }; + + const visit = (node: ts.Node): void => { + // 检查当前节点是否被忽略注释覆盖 + if (isNodeCoveredByIgnoreComment(node)) { + markNodeAndChildren(node); + return; + } + + // 特殊处理:JSX 表达式 + if (ts.isJsxExpression(node)) { + // 检查 JSX 表达式内部是否有忽略注释 + if (node.expression && isNodeCoveredByIgnoreComment(node.expression)) { + markNodeAndChildren(node.expression); + } + } + + // 特殊处理:条件表达式(三元运算符) + if (ts.isConditionalExpression(node)) { + // 检查 whenTrue 和 whenFalse 分支 + if (isNodeCoveredByIgnoreComment(node.whenTrue)) { + markNodeAndChildren(node.whenTrue); + } + if (isNodeCoveredByIgnoreComment(node.whenFalse)) { + markNodeAndChildren(node.whenFalse); + } + } + + // 特殊处理:二元表达式 + if (ts.isBinaryExpression(node)) { + // 检查左右操作数 + if (isNodeCoveredByIgnoreComment(node.left)) { + markNodeAndChildren(node.left); + } + if (isNodeCoveredByIgnoreComment(node.right)) { + markNodeAndChildren(node.right); } } @@ -760,6 +850,21 @@ export function performCustomChecks( return true; } + // 如果文件开头有忽略注释,整个文件都忽略 + const fileStartComments = ts.getLeadingCommentRanges( + sourceFile.getFullText(), + 0 + ); + + if (fileStartComments) { + for (const comment of fileStartComments) { + const commentText = sourceFile.getFullText().substring(comment.pos, comment.end); + if (isIgnoreComment(commentText)) { + return true; + } + } + } + // 向上查找父节点(最多3层) let current: ts.Node | undefined = node.parent; let depth = 0; @@ -2839,7 +2944,7 @@ export function performCustomChecks( // 普通格式的 key,但本地没有 i18n 文件 addDiagnostic( callNode, - `i18n key "${key}" 无法检查,因为未找到本地化文件: ${localePath}。`, + `i18n key "${key}" 无法检查,因为找不到locales文件: ${localePath}。`, 9202 ); } @@ -3368,4 +3473,4 @@ export const build = (pwd: string, args: any[]) => { options.project = configPath; compile(pwd, options); -} \ No newline at end of file +}