906 lines
42 KiB
JavaScript
906 lines
42 KiB
JavaScript
"use strict";
|
||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
}
|
||
Object.defineProperty(o, k2, desc);
|
||
}) : (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
o[k2] = m[k];
|
||
}));
|
||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
}) : function(o, v) {
|
||
o["default"] = v;
|
||
});
|
||
var __importStar = (this && this.__importStar) || (function () {
|
||
var ownKeys = function(o) {
|
||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||
var ar = [];
|
||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||
return ar;
|
||
};
|
||
return ownKeys(o);
|
||
};
|
||
return function (mod) {
|
||
if (mod && mod.__esModule) return mod;
|
||
var result = {};
|
||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||
__setModuleDefault(result, mod);
|
||
return result;
|
||
};
|
||
})();
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.CodeErrorProvider = void 0;
|
||
const vscode = __importStar(require("vscode"));
|
||
// 代码错误检测提供者类
|
||
class CodeErrorProvider {
|
||
// 提供代码诊断信息
|
||
static async provideDiagnostics(document, diagnostics) {
|
||
const diagnosticList = [];
|
||
let isInBlockComment = false; // 文档级别的块注释状态
|
||
// 全文档检查,支持多行结构
|
||
const documentText = document.getText();
|
||
const lines = documentText.split('\n');
|
||
// 检查未闭合的字符串(支持多行)
|
||
diagnosticList.push(...this.checkForUnclosedStrings(document));
|
||
// 检查未闭合的括号(支持多行)
|
||
diagnosticList.push(...this.checkForUnclosedBrackets(document));
|
||
// 逐行检查其他错误,传递块注释状态
|
||
for (let i = 0; i < document.lineCount; i++) {
|
||
const line = document.lineAt(i);
|
||
const result = this.checkLineForErrors(line, i, isInBlockComment);
|
||
diagnosticList.push(...result.diagnostics);
|
||
isInBlockComment = result.isInBlockComment; // 更新块注释状态
|
||
}
|
||
// 发布诊断信息
|
||
diagnostics.set(document.uri, diagnosticList);
|
||
}
|
||
// 检查单行代码错误,支持块注释状态跟踪
|
||
static checkLineForErrors(line, lineIndex, isInBlockComment) {
|
||
const diagnostics = [];
|
||
const lineText = line.text;
|
||
// 处理行注释和块注释状态
|
||
const commentInfo = this.processCommentsAndGetValidText(lineText, isInBlockComment);
|
||
// 如果整行都在注释中,直接返回
|
||
if (commentInfo.skipEntireLine) {
|
||
return { diagnostics, isInBlockComment: commentInfo.newBlockCommentState };
|
||
}
|
||
// 只对非注释部分进行错误检测
|
||
if (commentInfo.validText.trim().length > 0) {
|
||
// 检查常见的语法错误
|
||
diagnostics.push(...this.checkForCommonSyntaxErrorsWithValidText(lineText, commentInfo.validText, commentInfo.validRange, lineIndex));
|
||
// 检查可能的拼写错误
|
||
diagnostics.push(...this.checkForPossibleTyposWithValidText(lineText, commentInfo.validText, commentInfo.validRange, lineIndex));
|
||
}
|
||
return {
|
||
diagnostics,
|
||
isInBlockComment: commentInfo.newBlockCommentState
|
||
};
|
||
}
|
||
// 处理注释并获取有效的代码文本
|
||
static processCommentsAndGetValidText(lineText, isInBlockComment) {
|
||
let currentBlockCommentState = isInBlockComment;
|
||
// 如果整行都在块注释中,先检查是否有结束标记
|
||
if (currentBlockCommentState) {
|
||
const blockCommentEnd = lineText.indexOf('*/');
|
||
if (blockCommentEnd !== -1) {
|
||
// 找到块注释结束,从结束位置后开始处理
|
||
currentBlockCommentState = false;
|
||
const remainingLine = lineText.substring(blockCommentEnd + 2);
|
||
// 递归处理剩余部分
|
||
const remainingResult = this.processCommentsAndGetValidText(remainingLine, false);
|
||
return {
|
||
validText: remainingResult.validText,
|
||
validRange: {
|
||
start: blockCommentEnd + 2 + remainingResult.validRange.start,
|
||
end: blockCommentEnd + 2 + remainingResult.validRange.end
|
||
},
|
||
skipEntireLine: remainingResult.skipEntireLine,
|
||
newBlockCommentState: remainingResult.newBlockCommentState
|
||
};
|
||
}
|
||
else {
|
||
// 整行都在块注释中
|
||
return {
|
||
validText: '',
|
||
validRange: { start: 0, end: 0 },
|
||
skipEntireLine: true,
|
||
newBlockCommentState: true
|
||
};
|
||
}
|
||
}
|
||
// 现在处理这一行,移除所有注释,只保留有效代码部分
|
||
let result = '';
|
||
let validRanges = [];
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
let i = 0;
|
||
let currentValidStart = 0;
|
||
while (i < lineText.length) {
|
||
const char = lineText[i];
|
||
const nextChar = i < lineText.length - 1 ? lineText[i + 1] : '';
|
||
// 处理字符串状态
|
||
if (!inString) {
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
escaped = false;
|
||
}
|
||
}
|
||
else {
|
||
if (escaped) {
|
||
escaped = false;
|
||
i++;
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
i++;
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
inString = false;
|
||
stringChar = '';
|
||
i++;
|
||
continue;
|
||
}
|
||
i++;
|
||
continue;
|
||
}
|
||
// 只有在非字符串状态下才检查注释
|
||
if (!inString) {
|
||
// 检查行注释 // (优先级最高)
|
||
if (char === '/' && nextChar === '/') {
|
||
// 遇到行注释,保存当前有效部分,后续全部跳过
|
||
if (i > currentValidStart) {
|
||
validRanges.push({ start: currentValidStart, end: i });
|
||
result += lineText.substring(currentValidStart, i);
|
||
}
|
||
break; // 行注释后面的内容全部跳过
|
||
}
|
||
// 检查块注释开始 /*
|
||
if (char === '/' && nextChar === '*') {
|
||
// 保存块注释前的有效部分
|
||
if (i > currentValidStart) {
|
||
validRanges.push({ start: currentValidStart, end: i });
|
||
result += lineText.substring(currentValidStart, i);
|
||
}
|
||
// 寻找块注释结束
|
||
const blockCommentEnd = lineText.indexOf('*/', i + 2);
|
||
if (blockCommentEnd !== -1) {
|
||
// 同行找到块注释结束,跳过整个块注释
|
||
i = blockCommentEnd + 2;
|
||
currentValidStart = i;
|
||
continue;
|
||
}
|
||
else {
|
||
// 没有找到结束标记,块注释延续到下一行
|
||
currentBlockCommentState = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
i++;
|
||
}
|
||
// 添加最后的有效部分
|
||
if (!currentBlockCommentState && currentValidStart < lineText.length) {
|
||
validRanges.push({ start: currentValidStart, end: lineText.length });
|
||
result += lineText.substring(currentValidStart);
|
||
}
|
||
// 如果没有有效内容,返回空
|
||
if (validRanges.length === 0) {
|
||
return {
|
||
validText: '',
|
||
validRange: { start: 0, end: 0 },
|
||
skipEntireLine: true,
|
||
newBlockCommentState: currentBlockCommentState
|
||
};
|
||
}
|
||
// 返回第一个有效范围(简化处理,实际使用时会在所有有效范围中检查)
|
||
return {
|
||
validText: result,
|
||
validRange: { start: validRanges[0].start, end: validRanges[validRanges.length - 1].end },
|
||
skipEntireLine: result.trim().length === 0,
|
||
newBlockCommentState: currentBlockCommentState
|
||
};
|
||
}
|
||
// 检查指定位置是否在字符串中
|
||
static isInString(line, position) {
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
for (let i = 0; i < position && i < line.length; i++) {
|
||
const char = line[i];
|
||
if (!inString) {
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
escaped = false;
|
||
}
|
||
}
|
||
else {
|
||
if (escaped) {
|
||
escaped = false;
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
inString = false;
|
||
stringChar = '';
|
||
}
|
||
}
|
||
}
|
||
return inString;
|
||
}
|
||
// 基于有效文本范围检查语法错误
|
||
static checkForCommonSyntaxErrorsWithValidText(originalLine, validText, validRange, lineIndex) {
|
||
const diagnostics = [];
|
||
// 跳过空行
|
||
if (validText.trim().length === 0) {
|
||
return diagnostics;
|
||
}
|
||
// 由于validText可能是多个片段拼接的结果,我们需要直接在原始行中查找
|
||
// 但只检查那些不在注释和字符串中的位置
|
||
const typoPatterns = [
|
||
{ pattern: /\bloacl\b/g, correct: 'local', message: '拼写错误:应该是 "local" 而不是 "loacl"' },
|
||
{ pattern: /\bfunctoin\b/g, correct: 'function', message: '拼写错误:应该是 "function" 而不是 "functoin"' },
|
||
{ pattern: /\bretun\b/g, correct: 'return', message: '拼写错误:应该是 "return" 而不是 "retun"' },
|
||
{ pattern: /\bwhlie\b/g, correct: 'while', message: '拼写错误:应该是 "while" 而不是 "whlie"' },
|
||
{ pattern: /\belsee\b/g, correct: 'else', message: '拼写错误:应该是 "else" 而不是 "elsee"' },
|
||
{ pattern: /\bforech\b/g, correct: 'foreach', message: '拼写错误:应该是 "foreach" 而不是 "forech"' },
|
||
{ pattern: /\bclas\b/g, correct: 'class', message: '拼写错误:应该是 "class" 而不是 "clas"' },
|
||
{ pattern: /\bbreka\b/g, correct: 'break', message: '拼写错误:应该是 "break" 而不是 "breka"' },
|
||
{ pattern: /\bcontineu\b/g, correct: 'continue', message: '拼写错误:应该是 "continue" 而不是 "contineu"' }
|
||
];
|
||
typoPatterns.forEach(({ pattern, correct, message }) => {
|
||
let match;
|
||
// 在原始行中查找所有匹配
|
||
while ((match = pattern.exec(originalLine)) !== null) {
|
||
const matchPosition = match.index;
|
||
// 检查这个位置是否在有效的代码区域(不在注释或字符串中)
|
||
if (this.isPositionInValidCode(originalLine, matchPosition)) {
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, matchPosition), new vscode.Position(lineIndex, matchPosition + match[0].length));
|
||
const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
|
||
diagnostic.tags = [vscode.DiagnosticTag.Deprecated];
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
}
|
||
});
|
||
return diagnostics;
|
||
}
|
||
// 检查指定位置是否在有效代码中(不在注释或字符串中)
|
||
static isPositionInValidCode(line, position) {
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
let inBlockComment = false;
|
||
for (let i = 0; i < position && i < line.length; i++) {
|
||
const char = line[i];
|
||
const nextChar = i < line.length - 1 ? line[i + 1] : '';
|
||
// 如果在块注释中,检查是否遇到结束标记
|
||
if (inBlockComment) {
|
||
if (char === '*' && nextChar === '/') {
|
||
inBlockComment = false;
|
||
i++; // 跳过 '/'
|
||
}
|
||
continue;
|
||
}
|
||
// 处理字符串状态
|
||
if (!inString) {
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
escaped = false;
|
||
}
|
||
}
|
||
else {
|
||
if (escaped) {
|
||
escaped = false;
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
inString = false;
|
||
stringChar = '';
|
||
}
|
||
continue;
|
||
}
|
||
// 只有在非字符串状态下才检查注释
|
||
if (!inString) {
|
||
// 检查行注释
|
||
if (char === '/' && nextChar === '/') {
|
||
return false; // 位置在行注释中
|
||
}
|
||
// 检查块注释开始
|
||
if (char === '/' && nextChar === '*') {
|
||
inBlockComment = true;
|
||
i++; // 跳过 '*'
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
// 如果到达目标位置时仍在字符串或块注释中,则该位置无效
|
||
return !inString && !inBlockComment;
|
||
}
|
||
// 基于有效文本范围检查可能的拼写错误
|
||
static checkForPossibleTyposWithValidText(originalLine, validText, validRange, lineIndex) {
|
||
const diagnostics = [];
|
||
// 暂时空实现,可以后续扩展
|
||
return diagnostics;
|
||
}
|
||
// 检查常见的语法错误
|
||
static checkForCommonSyntaxErrors(lineText, lineIndex) {
|
||
const diagnostics = [];
|
||
// 跳过空行
|
||
const trimmedLine = lineText.trim();
|
||
if (trimmedLine.length === 0) {
|
||
return diagnostics;
|
||
}
|
||
// 检查是否使用了错误的关键字拼写(仅检查明显的拼写错误)
|
||
const typoPatterns = [
|
||
{ pattern: /\bloacl\b/g, correct: 'local', message: '拼写错误:应该是 "local" 而不是 "loacl"' },
|
||
{ pattern: /\bfunctoin\b/g, correct: 'function', message: '拼写错误:应该是 "function" 而不是 "functoin"' },
|
||
{ pattern: /\bretun\b/g, correct: 'return', message: '拼写错误:应该是 "return" 而不是 "retun"' },
|
||
{ pattern: /\bwhlie\b/g, correct: 'while', message: '拼写错误:应该是 "while" 而不是 "whlie"' },
|
||
{ pattern: /\belsee\b/g, correct: 'else', message: '拼写错误:应该是 "else" 而不是 "elsee"' },
|
||
{ pattern: /\bforech\b/g, correct: 'foreach', message: '拼写错误:应该是 "foreach" 而不是 "forech"' },
|
||
{ pattern: /\bclas\b/g, correct: 'class', message: '拼写错误:应该是 "class" 而不是 "clas"' },
|
||
{ pattern: /\bbreka\b/g, correct: 'break', message: '拼写错误:应该是 "break" 而不是 "breka"' },
|
||
{ pattern: /\bcontineu\b/g, correct: 'continue', message: '拼写错误:应该是 "continue" 而不是 "contineu"' }
|
||
];
|
||
typoPatterns.forEach(({ pattern, correct, message }) => {
|
||
let match;
|
||
while ((match = pattern.exec(lineText)) !== null) {
|
||
// 检查这个匹配是否在注释或字符串中
|
||
if (this.isPositionInCommentOrString(lineText, match.index)) {
|
||
continue;
|
||
}
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, match.index), new vscode.Position(lineIndex, match.index + match[0].length));
|
||
const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
|
||
// 添加快速修复信息
|
||
diagnostic.tags = [vscode.DiagnosticTag.Deprecated];
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
});
|
||
// 检查赋值操作符错误(使用 = 而不是 == 进行比较)
|
||
// 更精确的正则表达式,避免误报
|
||
const assignmentPattern = /\b(if|while)\s*\(\s*[^=<>!]*\s=(?!=)\s*[^=][^)]*\)/g;
|
||
let assignmentMatch;
|
||
while ((assignmentMatch = assignmentPattern.exec(lineText)) !== null) {
|
||
// 检查这个匹配是否在注释或字符串中
|
||
if (this.isPositionInCommentOrString(lineText, assignmentMatch.index)) {
|
||
continue;
|
||
}
|
||
// 排除明显的合法赋值情况
|
||
const matchText = assignmentMatch[0];
|
||
// 检查是否是函数调用或其他合法语法
|
||
if (!matchText.includes('()') && !matchText.includes('new ')) {
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, assignmentMatch.index), new vscode.Position(lineIndex, assignmentMatch.index + assignmentMatch[0].length));
|
||
const diagnostic = new vscode.Diagnostic(range, '可能的逻辑错误:在条件语句中使用了赋值操作符 "=" 而不是比较操作符 "=="', vscode.DiagnosticSeverity.Warning);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
}
|
||
// 检查常见的语法结构错误
|
||
// 检查函数定义语法
|
||
if (lineText.includes('function') && !lineText.match(/function\s+[a-zA-Z_]\w*\s*\(/)) {
|
||
const functionMatch = lineText.match(/function\s*\(/);
|
||
if (functionMatch && !this.isPositionInCommentOrString(lineText, functionMatch.index)) {
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, functionMatch.index), new vscode.Position(lineIndex, functionMatch.index + functionMatch[0].length));
|
||
const diagnostic = new vscode.Diagnostic(range, '语法错误:函数定义缺少函数名', vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
}
|
||
// 检查缺少分号的语句(仅对明显需要分号的语句检查)
|
||
const needsSemicolon = /\b(local|return|break|continue)\s+[^;\/]*[^;\s\/]$/;
|
||
if (needsSemicolon.test(trimmedLine) && !trimmedLine.endsWith(';') && !trimmedLine.endsWith('{')) {
|
||
// 确保这不是在注释中
|
||
const cleanLine = this.removeStringsAndComments(lineText);
|
||
if (cleanLine.trim()) {
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, lineText.length - 1), new vscode.Position(lineIndex, lineText.length));
|
||
const diagnostic = new vscode.Diagnostic(range, '可能缺少分号', vscode.DiagnosticSeverity.Information);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
}
|
||
return diagnostics;
|
||
}
|
||
// 检查未闭合的字符串(支持多行文档检查)
|
||
static checkForUnclosedStrings(document) {
|
||
const diagnostics = [];
|
||
const text = document.getText();
|
||
// 使用状态机来正确解析字符串
|
||
let inString = false;
|
||
let stringStartPos = null;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
let inLineComment = false;
|
||
let inBlockComment = false;
|
||
const lines = text.split('\n');
|
||
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
||
const line = lines[lineIndex];
|
||
// 检查是否整行都是注释(以 // 开头)
|
||
const trimmedLine = line.trim();
|
||
if (trimmedLine.startsWith('//')) {
|
||
inLineComment = true;
|
||
}
|
||
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
||
const char = line[charIndex];
|
||
const nextChar = charIndex < line.length - 1 ? line[charIndex + 1] : '';
|
||
// 处理注释
|
||
if (!inString) {
|
||
if (!inBlockComment && char === '/' && nextChar === '/') {
|
||
inLineComment = true;
|
||
charIndex++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
if (!inLineComment && char === '/' && nextChar === '*') {
|
||
inBlockComment = true;
|
||
charIndex++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
if (inBlockComment && char === '*' && nextChar === '/') {
|
||
inBlockComment = false;
|
||
charIndex++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
}
|
||
// 如果在注释中,跳过所有处理
|
||
if (inLineComment || inBlockComment) {
|
||
continue;
|
||
}
|
||
if (!inString) {
|
||
// 检查字符串开始
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
stringStartPos = new vscode.Position(lineIndex, charIndex);
|
||
escaped = false;
|
||
}
|
||
}
|
||
else {
|
||
// 在字符串中
|
||
if (escaped) {
|
||
escaped = false;
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
// 字符串结束
|
||
inString = false;
|
||
stringStartPos = null;
|
||
stringChar = '';
|
||
}
|
||
}
|
||
}
|
||
// 行结束处理
|
||
inLineComment = false; // 行注释在行末结束
|
||
// 只有在非注释行且有未闭合字符串时才报告错误
|
||
if (inString && stringStartPos && stringStartPos.line === lineIndex && !trimmedLine.startsWith('//')) {
|
||
// 如果字符串在当前行开始但没有结束,报告错误
|
||
const range = new vscode.Range(stringStartPos, new vscode.Position(lineIndex, line.length));
|
||
const diagnostic = new vscode.Diagnostic(range, `字符串未正确闭合:缺少结束${stringChar === '"' ? '双' : '单'}引号`, vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(diagnostic);
|
||
// 重置状态,避免后续行都报错
|
||
inString = false;
|
||
stringStartPos = null;
|
||
}
|
||
}
|
||
// 如果文档结束时还有未闭合的字符串,并且不在注释中
|
||
if (inString && stringStartPos) {
|
||
const lastLine = lines.length - 1;
|
||
const lastLineTrimmed = lines[lastLine].trim();
|
||
if (!lastLineTrimmed.startsWith('//')) {
|
||
const range = new vscode.Range(stringStartPos, new vscode.Position(lastLine, lines[lastLine].length));
|
||
const diagnostic = new vscode.Diagnostic(range, `字符串未正确闭合:缺少结束${stringChar === '"' ? '双' : '单'}引号`, vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
}
|
||
return diagnostics;
|
||
}
|
||
// 检查未闭合的括号(支持多行文档检查)
|
||
static checkForUnclosedBrackets(document) {
|
||
const diagnostics = [];
|
||
const text = document.getText();
|
||
// 括号栈,记录每个开括号的位置和类型
|
||
const bracketStack = [];
|
||
const bracketPairs = [
|
||
{ open: '(', close: ')', name: '圆括号' },
|
||
{ open: '[', close: ']', name: '方括号' },
|
||
{ open: '{', close: '}', name: '花括号' }
|
||
];
|
||
// 状态追踪
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
let inLineComment = false;
|
||
let inBlockComment = false;
|
||
const lines = text.split('\n');
|
||
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
||
const line = lines[lineIndex];
|
||
// 检查是否整行都是注释(以 // 开头)
|
||
const trimmedLine = line.trim();
|
||
const isCommentLine = trimmedLine.startsWith('//');
|
||
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
||
const char = line[charIndex];
|
||
const nextChar = charIndex < line.length - 1 ? line[charIndex + 1] : '';
|
||
// 处理注释
|
||
if (!inString) {
|
||
if (!inBlockComment && char === '/' && nextChar === '/') {
|
||
inLineComment = true;
|
||
charIndex++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
if (!inLineComment && char === '/' && nextChar === '*') {
|
||
inBlockComment = true;
|
||
charIndex++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
if (inBlockComment && char === '*' && nextChar === '/') {
|
||
inBlockComment = false;
|
||
charIndex++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
}
|
||
// 如果在注释中,跳过所有处理
|
||
if (inLineComment || inBlockComment || isCommentLine) {
|
||
continue;
|
||
}
|
||
// 处理字符串状态
|
||
if (!inString) {
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
escaped = false;
|
||
}
|
||
}
|
||
else {
|
||
if (escaped) {
|
||
escaped = false;
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
inString = false;
|
||
stringChar = '';
|
||
}
|
||
continue; // 在字符串中,跳过括号检查
|
||
}
|
||
// 检查括号(只在非字符串、非注释中)
|
||
if (!inString && !inLineComment && !inBlockComment && !isCommentLine) {
|
||
const openBracket = bracketPairs.find(bp => bp.open === char);
|
||
if (openBracket) {
|
||
bracketStack.push({
|
||
char: char,
|
||
position: new vscode.Position(lineIndex, charIndex),
|
||
name: openBracket.name,
|
||
inComment: false
|
||
});
|
||
}
|
||
else {
|
||
const closeBracket = bracketPairs.find(bp => bp.close === char);
|
||
if (closeBracket) {
|
||
if (bracketStack.length === 0) {
|
||
// 多余的闭合括号
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, charIndex), new vscode.Position(lineIndex, charIndex + 1));
|
||
const diagnostic = new vscode.Diagnostic(range, `多余的${closeBracket.name}闭合符`, vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
else {
|
||
const lastOpen = bracketStack.pop();
|
||
const expectedClose = bracketPairs.find(bp => bp.open === lastOpen.char)?.close;
|
||
if (expectedClose !== char) {
|
||
// 括号不匹配
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, charIndex), new vscode.Position(lineIndex, charIndex + 1));
|
||
const diagnostic = new vscode.Diagnostic(range, `括号不匹配:期望 "${expectedClose}" 但找到 "${char}"`, vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(diagnostic);
|
||
// 也标记未匹配的开括号
|
||
const openRange = new vscode.Range(lastOpen.position, new vscode.Position(lastOpen.position.line, lastOpen.position.character + 1));
|
||
const openDiagnostic = new vscode.Diagnostic(openRange, `未匹配的${lastOpen.name}`, vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(openDiagnostic);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 行结束处理
|
||
inLineComment = false; // 行注释在行末结束
|
||
}
|
||
// 检查未闭合的开括号(排除在注释中的括号)
|
||
bracketStack.forEach(bracket => {
|
||
if (!bracket.inComment) {
|
||
const range = new vscode.Range(bracket.position, new vscode.Position(bracket.position.line, bracket.position.character + 1));
|
||
const diagnostic = new vscode.Diagnostic(range, `未闭合的${bracket.name}`, vscode.DiagnosticSeverity.Error);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
});
|
||
return diagnostics;
|
||
}
|
||
// 检查可能的拼写错误
|
||
static checkForPossibleTypos(lineText, lineIndex) {
|
||
const diagnostics = [];
|
||
// 跳过空行
|
||
const trimmedLine = lineText.trim();
|
||
if (trimmedLine.length === 0) {
|
||
return diagnostics;
|
||
}
|
||
// 更完整的Squirrel关键字列表
|
||
const squirrelKeywords = [
|
||
'function', 'local', 'if', 'else', 'while', 'for', 'foreach', 'in',
|
||
'return', 'break', 'continue', 'class', 'extends', 'constructor',
|
||
'null', 'true', 'false', 'try', 'catch', 'throw', 'const',
|
||
'enum', 'switch', 'case', 'default', 'delete', 'typeof',
|
||
'clone', 'resume', 'yield', 'static', 'this', 'base'
|
||
];
|
||
// 提取标识符(避免数字、操作符等)
|
||
const wordPattern = /\b[a-zA-Z_]\w{2,}\b/g;
|
||
let match;
|
||
while ((match = wordPattern.exec(lineText)) !== null) {
|
||
const word = match[0];
|
||
const wordLower = word.toLowerCase();
|
||
const startIndex = match.index;
|
||
// 检查这个单词是否在注释或字符串中
|
||
if (this.isPositionInCommentOrString(lineText, startIndex)) {
|
||
continue;
|
||
}
|
||
// 跳过已知的关键字(正确拼写)
|
||
if (squirrelKeywords.includes(wordLower)) {
|
||
continue;
|
||
}
|
||
// 跳过常见的标识符模式(避免误报)
|
||
if (this.isLikelyValidIdentifier(word)) {
|
||
continue;
|
||
}
|
||
// 检查是否与关键字相似
|
||
const similarKeyword = squirrelKeywords.find(keyword => {
|
||
const distance = this.editDistance(wordLower, keyword);
|
||
// 只对编辑距离为1-2且长度相近的单词提示
|
||
return distance >= 1 && distance <= 2 &&
|
||
Math.abs(word.length - keyword.length) <= 2;
|
||
});
|
||
if (similarKeyword) {
|
||
const range = new vscode.Range(new vscode.Position(lineIndex, startIndex), new vscode.Position(lineIndex, startIndex + word.length));
|
||
const diagnostic = new vscode.Diagnostic(range, `可能的拼写错误:您是否想输入 "${similarKeyword}"?`, vscode.DiagnosticSeverity.Hint);
|
||
diagnostics.push(diagnostic);
|
||
}
|
||
}
|
||
return diagnostics;
|
||
}
|
||
// 检查指定位置是否在注释或字符串中
|
||
static isPositionInCommentOrString(line, position) {
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
let inBlockComment = false;
|
||
for (let i = 0; i < position && i < line.length; i++) {
|
||
const char = line[i];
|
||
const nextChar = i < line.length - 1 ? line[i + 1] : '';
|
||
if (inBlockComment) {
|
||
if (char === '*' && nextChar === '/') {
|
||
inBlockComment = false;
|
||
i++; // 跳过 '/'
|
||
}
|
||
continue;
|
||
}
|
||
// 检查行注释
|
||
if (char === '/' && nextChar === '/') {
|
||
return true; // 位置在行注释中
|
||
}
|
||
// 检查块注释开始
|
||
if (char === '/' && nextChar === '*') {
|
||
inBlockComment = true;
|
||
i++; // 跳过 '*'
|
||
continue;
|
||
}
|
||
if (!inString) {
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
escaped = false;
|
||
}
|
||
}
|
||
else {
|
||
if (escaped) {
|
||
escaped = false;
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
inString = false;
|
||
stringChar = '';
|
||
}
|
||
}
|
||
}
|
||
return inString || inBlockComment;
|
||
}
|
||
// 移除字符串和注释内容,避免对其中的内容进行拼写检查
|
||
static removeStringsAndComments(line) {
|
||
let result = '';
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let escaped = false;
|
||
let inLineComment = false;
|
||
let inBlockComment = false;
|
||
for (let i = 0; i < line.length; i++) {
|
||
const char = line[i];
|
||
const nextChar = i < line.length - 1 ? line[i + 1] : '';
|
||
// 如果已经在行注释中,所有后续字符都用空格替换
|
||
if (inLineComment) {
|
||
result += ' ';
|
||
continue;
|
||
}
|
||
// 如果已经在块注释中,检查结束标记
|
||
if (inBlockComment) {
|
||
if (char === '*' && nextChar === '/') {
|
||
inBlockComment = false;
|
||
result += ' '; // 用空格替换 */
|
||
i++; // 跳过下一个字符
|
||
}
|
||
else {
|
||
result += ' '; // 用空格替换块注释内容
|
||
}
|
||
continue;
|
||
}
|
||
if (!inString) {
|
||
// 检查行注释开始
|
||
if (char === '/' && nextChar === '/') {
|
||
inLineComment = true;
|
||
result += ' '; // 用空格替换 //
|
||
i++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
// 检查块注释开始
|
||
if (char === '/' && nextChar === '*') {
|
||
inBlockComment = true;
|
||
result += ' '; // 用空格替换 /*
|
||
i++; // 跳过下一个字符
|
||
continue;
|
||
}
|
||
// 检查字符串开始
|
||
if (char === '"' || char === "'") {
|
||
inString = true;
|
||
stringChar = char;
|
||
escaped = false;
|
||
result += ' '; // 用空格替换字符串内容
|
||
continue;
|
||
}
|
||
}
|
||
else {
|
||
// 在字符串中处理转义和结束
|
||
if (escaped) {
|
||
escaped = false;
|
||
result += ' ';
|
||
continue;
|
||
}
|
||
if (char === '\\') {
|
||
escaped = true;
|
||
result += ' ';
|
||
continue;
|
||
}
|
||
if (char === stringChar) {
|
||
inString = false;
|
||
stringChar = '';
|
||
result += ' ';
|
||
continue;
|
||
}
|
||
result += ' '; // 用空格替换字符串内容
|
||
continue;
|
||
}
|
||
// 保留非字符串、非注释的字符
|
||
result += char;
|
||
}
|
||
return result;
|
||
}
|
||
// 判断是否是可能的有效标识符(减少误报)
|
||
static isLikelyValidIdentifier(word) {
|
||
// 常见的有效标识符模式
|
||
const validPatterns = [
|
||
/^[A-Z][A-Z_0-9]+$/, // 常量名(全大写)
|
||
/^[a-z][a-zA-Z0-9]*$/, // 驼峰命名
|
||
/^[a-z]+_[a-z0-9_]*$/, // 下划线命名
|
||
/^[A-Z][a-zA-Z0-9]*$/, // 帕斯卡命名
|
||
/^\w+\d+$/, // 带数字结尾
|
||
/^get[A-Z]/, // getter方法
|
||
/^set[A-Z]/, // setter方法
|
||
/^is[A-Z]/, // 布尔方法
|
||
/^has[A-Z]/, // 检查方法
|
||
];
|
||
return validPatterns.some(pattern => pattern.test(word));
|
||
}
|
||
// 计算两个字符串之间的编辑距离(Levenshtein距离)
|
||
static editDistance(str1, str2) {
|
||
const matrix = [];
|
||
// 初始化矩阵
|
||
for (let i = 0; i <= str2.length; i++) {
|
||
matrix[i] = [i];
|
||
}
|
||
for (let j = 0; j <= str1.length; j++) {
|
||
matrix[0][j] = j;
|
||
}
|
||
// 填充矩阵
|
||
for (let i = 1; i <= str2.length; i++) {
|
||
for (let j = 1; j <= str1.length; j++) {
|
||
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
||
matrix[i][j] = matrix[i - 1][j - 1];
|
||
}
|
||
else {
|
||
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // 替换
|
||
matrix[i][j - 1] + 1, // 插入
|
||
matrix[i - 1][j] + 1 // 删除
|
||
);
|
||
}
|
||
}
|
||
}
|
||
return matrix[str2.length][str1.length];
|
||
}
|
||
// 实现CodeActionProvider接口
|
||
async provideCodeActions(document, range, context, token) {
|
||
const actions = [];
|
||
// 为诊断问题提供快速修复
|
||
for (const diagnostic of context.diagnostics) {
|
||
// 拼写错误快速修复
|
||
if (diagnostic.message.includes('可能的拼写错误:您是否想输入')) {
|
||
const match = diagnostic.message.match(/您是否想输入 "([^"]+)"\?/);
|
||
if (match) {
|
||
const correctWord = match[1];
|
||
const action = new vscode.CodeAction(`将 "${document.getText(diagnostic.range)}" 更正为 "${correctWord}"`, vscode.CodeActionKind.QuickFix);
|
||
action.edit = new vscode.WorkspaceEdit();
|
||
action.edit.replace(document.uri, diagnostic.range, correctWord);
|
||
action.isPreferred = true;
|
||
actions.push(action);
|
||
}
|
||
}
|
||
// 明显拼写错误快速修复
|
||
else if (diagnostic.message.includes('拼写错误:应该是')) {
|
||
const match = diagnostic.message.match(/应该是 "([^"]+)" 而不是 "[^"]+"/);
|
||
if (match) {
|
||
const correctWord = match[1];
|
||
const action = new vscode.CodeAction(`更正拼写为 "${correctWord}"`, vscode.CodeActionKind.QuickFix);
|
||
action.edit = new vscode.WorkspaceEdit();
|
||
action.edit.replace(document.uri, diagnostic.range, correctWord);
|
||
action.isPreferred = true;
|
||
actions.push(action);
|
||
}
|
||
}
|
||
// 添加分号快速修复
|
||
else if (diagnostic.message.includes('可能缺少分号')) {
|
||
const action = new vscode.CodeAction('添加分号', vscode.CodeActionKind.QuickFix);
|
||
action.edit = new vscode.WorkspaceEdit();
|
||
const line = document.lineAt(diagnostic.range.start.line);
|
||
const endPos = new vscode.Position(line.lineNumber, line.text.length);
|
||
action.edit.insert(document.uri, endPos, ';');
|
||
actions.push(action);
|
||
}
|
||
// 修复赋值操作符错误
|
||
else if (diagnostic.message.includes('使用了赋值操作符 "=" 而不是比较操作符')) {
|
||
const action = new vscode.CodeAction('将 "=" 更改为 "=="', vscode.CodeActionKind.QuickFix);
|
||
action.edit = new vscode.WorkspaceEdit();
|
||
const text = document.getText(diagnostic.range);
|
||
const fixedText = text.replace(/([^=])=([^=])/g, '$1==$2');
|
||
action.edit.replace(document.uri, diagnostic.range, fixedText);
|
||
actions.push(action);
|
||
}
|
||
}
|
||
return actions;
|
||
}
|
||
}
|
||
exports.CodeErrorProvider = CodeErrorProvider;
|
||
//# sourceMappingURL=codeErrorProvider.js.map
|