2025-06-23 21:03:09 +08:00

343 lines
9.5 KiB
TypeScript

/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { linter } from '@codemirror/lint';
import * as acorn from 'acorn';
import { simple as walk } from 'acorn-walk';
interface CommentDirectives {
globalDirectives: string[];
ignoreRanges: Array<{ start: number; end: number }>;
}
/**
* 解析代码中的特殊注释指令
*/
const parseCommentDirectives = (code: string): CommentDirectives => {
const globalDirectives: string[] = [];
const ignoreRanges: Array<{ start: number; end: number }> = [];
// 匹配 /* global var1, var2 */ 或 // global var1, var2
const globalRegex = /(?:\/\*\s*global\s+([^*]+)\*\/|\/\/\s*global\s+(.+))/gi;
let match;
while ((match = globalRegex.exec(code)) !== null) {
const varsText = match[1] || match[2];
if (varsText) {
const vars = varsText
.split(',')
.map((v) => v.trim())
.filter((v) => v);
globalDirectives.push(...vars);
}
}
// 匹配 /* eslint-disable */ 或 // eslint-disable-line
const disableRegex = /(?:\/\*\s*eslint-disable(?:\s+[^*]+)?\s*\*\/|\/\/\s*eslint-disable-line)/gi;
while ((match = disableRegex.exec(code)) !== null) {
// 找到禁用注释的行
const lines = code.substring(0, match.index).split('\n');
const lineNumber = lines.length - 1;
const lineStart = lines.slice(0, lineNumber).join('\n').length + (lineNumber > 0 ? 1 : 0);
const lineEnd = lineStart + (lines[lineNumber]?.length || 0);
ignoreRanges.push({ start: lineStart, end: lineEnd });
}
return { globalDirectives, ignoreRanges };
};
/**
* 获取默认的全局变量集合
*/
const getDefaultGlobals = (): Set<string> => {
return new Set([
// lowcode 环境提供的变量
'element',
'ctx',
'model',
'requirejs',
'requireAsync',
'loadCSS',
// JavaScript 内置对象
'console',
'window',
'document',
'setTimeout',
'setInterval',
'clearTimeout',
'clearInterval',
'Promise',
'Array',
'Object',
'String',
'Number',
'Boolean',
'Date',
'Math',
'JSON',
'Error',
'TypeError',
'ReferenceError',
'SyntaxError',
// 常用的全局函数
'parseInt',
'parseFloat',
'isNaN',
'isFinite',
'encodeURIComponent',
'decodeURIComponent',
'fetch',
'XMLHttpRequest',
'FormData',
'URLSearchParams',
// 现代 JavaScript
'Map',
'Set',
'WeakMap',
'WeakSet',
'Symbol',
'Proxy',
'Reflect',
// 异步相关
'async',
'await',
// 常用库可能暴露的全局变量
'echarts',
'Chart',
'_',
'lodash',
'$',
'jQuery',
'moment',
]);
};
/**
* 收集代码中定义的变量
*/
const collectDefinedVariables = (ast: any, availableGlobals: Set<string>): Set<string> => {
const definedVariables = new Set(availableGlobals);
try {
walk(ast, {
// 变量声明
VariableDeclarator(node) {
if (node && node.id && node.id.type === 'Identifier' && node.id.name) {
definedVariables.add(node.id.name);
}
},
// 函数声明
FunctionDeclaration(node) {
if (node && node.id && node.id.name) {
definedVariables.add(node.id.name);
}
// 函数参数
if (node && node.params && Array.isArray(node.params)) {
node.params.forEach((param) => {
if (param && param.type === 'Identifier' && param.name) {
definedVariables.add(param.name);
}
});
}
},
// 函数表达式参数
FunctionExpression(node) {
if (node && node.id && node.id.name) {
definedVariables.add(node.id.name);
}
if (node && node.params && Array.isArray(node.params)) {
node.params.forEach((param) => {
if (param && param.type === 'Identifier' && param.name) {
definedVariables.add(param.name);
}
});
}
},
// 箭头函数参数
ArrowFunctionExpression(node) {
if (node && node.params && Array.isArray(node.params)) {
node.params.forEach((param) => {
if (param && param.type === 'Identifier' && param.name) {
definedVariables.add(param.name);
}
});
}
},
});
} catch (e) {
console.warn('Variable collection failed:', e);
}
return definedVariables;
};
/**
* 检查标识符使用情况
*/
const checkIdentifierUsage = (
ast: any,
definedVariables: Set<string>,
shouldIgnoreError: (position: number) => boolean,
) => {
const diagnostics: any[] = [];
try {
walk(ast, {
Identifier(node, ancestors) {
// 基本验证
if (!node || !node.name || typeof node.name !== 'string') {
return;
}
// 确保有父节点信息
if (!ancestors || !Array.isArray(ancestors) || ancestors.length < 2) {
// 如果没有父节点信息,假设是变量使用并检查
if (!definedVariables.has(node.name)) {
const errorPos = node.start || 0;
if (!shouldIgnoreError(errorPos)) {
diagnostics.push({
from: errorPos,
to: node.end || errorPos + node.name.length,
severity: 'error',
message: `'${node.name}' is not defined`,
actions: [],
});
}
}
return;
}
// 检查是否在定义位置(忽略定义时的标识符)
const parent = ancestors[ancestors.length - 2];
if (!parent || !parent.type) {
// 没有父节点类型信息,假设是变量使用
if (!definedVariables.has(node.name)) {
const errorPos = node.start || 0;
if (!shouldIgnoreError(errorPos)) {
diagnostics.push({
from: errorPos,
to: node.end || errorPos + node.name.length,
severity: 'error',
message: `'${node.name}' is not defined`,
actions: [],
});
}
}
return;
}
const isDefinition =
(parent.type === 'VariableDeclarator' && parent.id === node) ||
(parent.type === 'FunctionDeclaration' && parent.id === node) ||
(parent.type === 'Property' && parent.key === node && !parent.computed) ||
(parent.type === 'MemberExpression' && parent.property === node && !parent.computed) ||
(parent.type === 'AssignmentPattern' && parent.left === node) ||
parent.type === 'ImportDefaultSpecifier' ||
parent.type === 'ImportSpecifier';
// 如果不是定义位置,且变量未定义,则报错
if (!isDefinition && !definedVariables.has(node.name)) {
const errorPos = node.start || 0;
// 检查是否应该忽略这个错误
if (!shouldIgnoreError(errorPos)) {
diagnostics.push({
from: errorPos,
to: node.end || errorPos + node.name.length,
severity: 'error',
message: `'${node.name}' is not defined`,
actions: [],
});
}
}
},
});
} catch (e) {
console.warn('Identifier checking failed:', e);
}
return diagnostics;
};
/**
* 创建 JavaScript 语法检查器
*/
export const createJavaScriptLinter = () => {
return linter((view) => {
const diagnostics: any[] = [];
const text = view.state.doc.toString();
// 如果代码为空,不进行检查
if (!text.trim()) {
return diagnostics;
}
// 解析注释指令
const { globalDirectives, ignoreRanges } = parseCommentDirectives(text);
// 定义可用的全局变量
const availableGlobals = getDefaultGlobals();
// 添加注释中声明的全局变量
globalDirectives.forEach((varName) => availableGlobals.add(varName));
// 检查错误是否应该被忽略
const shouldIgnoreError = (position: number): boolean => {
return ignoreRanges.some((range) => position >= range.start && position <= range.end);
};
try {
// 使用 acorn 解析代码
const ast = acorn.parse(text, {
ecmaVersion: 2022,
sourceType: 'script',
allowAwaitOutsideFunction: true,
locations: true,
});
// 检查 AST 是否有效
if (!ast || typeof ast !== 'object') {
return diagnostics;
}
// 收集所有变量声明
const definedVariables = collectDefinedVariables(ast, availableGlobals);
// 检查标识符使用
const identifierDiagnostics = checkIdentifierUsage(ast, definedVariables, shouldIgnoreError);
diagnostics.push(...identifierDiagnostics);
} catch (error: any) {
// 语法错误
let from = 0;
let to = text.length;
// 尝试解析位置信息
if (error.loc) {
const lines = text.split('\n');
from = lines.slice(0, error.loc.line - 1).join('\n').length + (error.loc.line > 1 ? 1 : 0) + error.loc.column;
to = from + 1;
} else if (error.pos !== undefined) {
from = error.pos;
to = from + 1;
}
diagnostics.push({
from,
to,
severity: 'error',
message: `Syntax error: ${error.message}`,
actions: [],
});
}
return diagnostics;
});
};