@lcap/nasl
Version:
NetEase Application Specific Language
1,163 lines • 180 kB
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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var NaslServer_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NaslServer = exports.getDisplayString2Type = void 0;
const config_1 = require("../config");
const nasl_sentry_1 = require("@lcap/nasl-sentry");
const Messager_1 = __importDefault(require("../common/Messager"));
const oql_cache_1 = require("./OQL/oql-cache");
const initial_1 = require("../service/initial");
const set_1 = require("mnemonist/set");
const sqlFunctions_json_1 = __importDefault(require("./OQL/sqlFunctions.json"));
const sqlCategory_json_1 = __importDefault(require("./OQL/sqlCategory.json"));
/// #if process.env.BUILD_TARGET === 'node'
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const worker_threads_1 = require("worker_threads");
/// #endif
const concepts_1 = require("../concepts");
const asserts_1 = require("@lcap/nasl-concepts/asserts");
const service_1 = require("@lcap/nasl-concepts/service");
const utils = __importStar(require("../utils"));
const memory_optimization_1 = require("../generator/release-body/memory-optimization");
const createUiTs_1 = __importStar(require("./createUiTs"));
const translator_1 = require("../translator");
const translator_2 = require("./translator");
const common_1 = require("../common");
const diagnostic_1 = require("../manager/diagnostic");
const naslStdlibMap_1 = __importDefault(require("./naslStdlibMap"));
const decorators_1 = require("../decorators");
const nasl_concepts_1 = require("@lcap/nasl-concepts");
const nasl_language_server_core_1 = require("@lcap/nasl-language-server-core");
const findReference_1 = require("./findReference");
const NEED_SYNC_BIND_ATTRIBUTE = [
'textField',
'valueField',
'iconField',
'toField',
'parentField',
'childrenField',
'hasChildrenField',
'field',
'idField',
'titleField',
'closableField',
'descriptionField',
'expandedField',
'disabledField',
'nameField',
'labelField',
'sortField'
];
const SentryMessager = (0, nasl_sentry_1.sentryMonitorTSWorkerMessager)(Messager_1.default);
// naslStdlib文件缓存;作为全局变量给多实例用复用
const __naslStdlibFileCacheMap = new Map();
function transformDiagnosticMapToRecords(diagnosticMap) {
const records = [];
diagnosticMap.forEach((diagnostics, fileNode) => {
records.push(...nasl_language_server_core_1.checker.transformDiagnosticsToRecords(fileNode, diagnostics));
});
return records;
}
// 联合类型切割取出类型
function getDisplayString2Type(displayString) {
const targetString = displayString.match(/value:\s(\S+)\)/)?.[1];
let targetType = null;
if (targetString?.startsWith('nasl.core.')) {
targetType = targetString.slice(10);
}
// 取出匹配的内容
const reg = /<([^()]+)>/g;
// 解决extends 导致类型缺失的问题
displayString = displayString?.replace('T extends ', '') || '';
const types = reg.exec(displayString);
// 取出提示的类型,组成是数组
const typeList = (types?.[1].split(' | ') ?? []).map((item) => {
if (/<([^()]+)>/g.exec(item)) {
return item;
}
if (item.includes(' ')) {
const strs = item.split(' ');
const type = strs[strs.length - 1];
return type;
}
if (item.includes('.')) {
const strs = item.split('.');
const type = strs[strs.length - 1];
return type;
}
return item;
});
if (targetType) {
return typeList.filter((item) => item !== targetType);
}
return typeList;
}
exports.getDisplayString2Type = getDisplayString2Type;
function isCoreDateTimeType(n) {
return n?.expression?.__TypeAnnotation?.typeName === 'DateTime' &&
n?.expression?.__TypeAnnotation?.typeNamespace === 'nasl.core';
}
function isFunctionWithFixedTimeZoneParam(calleeName) {
const fns = ['CurrDateTime', 'CurrDate', 'CurrTime', 'FormatDateTime'];
return fns.includes(calleeName);
}
const createWorker = async (link, options) => {
const blob = await fetch(link).then((res) => res.blob());
const instance = globalThis.window;
const electron = instance.electron;
if (electron?.createWorker) {
return electron?.createWorker?.(await blob.arrayBuffer(), options);
}
const url = URL.createObjectURL(blob);
const worker = new worker_threads_1.Worker(url, options);
URL.revokeObjectURL(url);
return worker;
};
const allComponent = {};
let NaslServer = NaslServer_1 = class NaslServer {
constructor(opt) {
/** naslStdlib文件缓存 */
this.naslStdlibFileCacheMap = __naslStdlibFileCacheMap;
/**
* 按道理 fileSourceMap: new Map<string, SourceMap>() 比较合理,
* 但是要多维护一层 vertex 增删 -> file 增删的关系问题,
* 先用挂在点上面,简单的方法实现,后期再考虑解耦
*/
this.file2NodeMap = new Map();
/** TS 翻译的源码 */
this.tsFiles = new Map();
/// #if process.env.NODE_ENV === 'development'
/**
* 调试时是否储存 ts 文件
*/
this.openDebugEmbedded = true;
/// #endif
this.elementsLogic = {};
// 待处理的变化事件
this.changeStackList = [];
// 需要检查的文件节点
this.typerCheckFiles = new Set();
// 删除的精确节点,这个不一定是文件级别的节点
this.typerRemoveNodes = new Set();
// 创建的节点
this.typerCreateNodes = new Set();
// 临时 hack:OQL 需要删除的节点(NASL 节点重命名,老名字对应的 TS 节点需删除
this.oqlRemoveNodesTsPath = new Set();
// 临时 hack:删除的连接器中的文件级别的节点
this.removedConnectors = new Set();
// 变动的原始目标节点,带有 proxy,用于触发响应式更新:事件树,子逻辑树等
this.originProxyTargetNodes = new Set();
this.typerErrRecords = [];
// 确保首屏检查没有"传统艺能"、"时序问题"
this.cachedEventPayloadValues = [];
// 当前重命名模式:rename-only: 只重命名,update: 更新引用
this.currentRenameMode = null;
this.oqlDisaster = false;
this.useCache = false; // 是否使用本地缓存文件,目前用于AI沙箱无法调用接口的环境
this.isFirstScreen = true; // 优化首屏 getAncestor 热点代码
/**
* 语言服务运行完毕
*
* @description 初始化以及每次修改内容时都会重置
*/
this.lsRunEnd = Promise.resolve();
/**
* 语言服务运行完毕标记
*
* @description `lsRunEnd`属性的内部状态
*/
this._lsRunEndSwitch = () => void 0;
this.flags = {
profileMode: false
};
// 检查共享数据是否有更新
this._latestVersionsOfSharedApp = [];
this.firstScreenEndWork = undefined;
// 用户点击"查找引用"
this.findReferenceAndRender = (nd) => (0, findReference_1.findReferenceAndRender)(this.semEnv, nd);
/**
* 根据入参准备并往 TS Server 写入需要校验的 ts 文件
* @param chkFiles 需要校验的文件
* @param removedNodes 需要删除的文件
* @param oqlRemoveNodesTsPath 需要删除的文件路径
* @returns 需要校验的文件名
*/
this.prepareAndWriteTsFilesToCheck = async (oqlFiles, removedNodes, oqlRemoveNodesTsPath) => {
function getETSFilePath(fileNode) {
try {
return fileNode.getEmbeddedFilePath();
}
catch (err) {
return null;
}
}
async function removeETSFile(filePath) {
if (!filePath) {
return;
}
await self.writeFiles([
{
file: filePath,
fileContent: '',
},
]);
}
const self = this;
for (const nd of removedNodes) {
await removeETSFile(getETSFilePath(nd));
}
for (const filePath of oqlRemoveNodesTsPath) {
await removeETSFile(filePath);
}
const tsFilePathsToCheck = new Set();
for (const fileNode of oqlFiles) {
let tsFilePath;
try {
tsFilePath = fileNode.getEmbeddedFilePath();
}
catch (err) {
this.logger.error(err);
// TODO wudengke 或许需要 continue
}
let tsFile;
// 3.14 需要改成后端大逻辑涉及 OQL 改动时则收集一下(可能要跑 OQL 的 toTS)
// TODO: 可能可以用 fileNodeRaw 当输入,提速 5x,现在的 typer 已经有不触发 vue 渲染更新的情况了,可以考虑统一解决
await utils.timeSlicingWithGenerator(this.semData.updateSemanticCtxAsPer(fileNode));
// 用 raw 的话,很多响应式更新都失效了。几个,几十个的单子,实在是修补过来,慢慢修或者等 nasl worker 吧。
tsFile = await utils.timeSlicingWithGenerator(fileNode.toEmbeddedTSFile());
await this.writeFiles([{
file: tsFile.filePath,
fileContent: tsFile.code,
}]);
tsFilePathsToCheck.add(tsFilePath);
// 如果当前没有生成tsFile
if (!tsFile?.sourceMap) {
continue;
}
// 修改触发修改文件
this._debugInFileStorage(fileNode, [{
file: tsFile.filePath,
fileContent: tsFile.code,
}]);
// @ts-ignore
fileNode.sourceMap = tsFile.sourceMap;
// 麻了,查了半天是没 set 这里
this.file2NodeMap.set(tsFile.filePath, fileNode);
}
if (utils.isDebugMode) {
console.info('TS 校验以下文件', Array.from(oqlFiles).map((item) => item.getEmbeddedFilePath()));
}
utils.isDebugMode && console.timeEnd('TS 文件变更');
return { tsFilePathsToCheck };
};
this.getProxyNode = (nd) => {
return this.getProxyApp()?.findNodeByPath(nd.nodePath);
};
this.promise = this.launch(opt);
}
async start() {
await this.promise;
return this.messager.requestCommand('start');
}
terminate() {
this.worker.terminate();
this.worker = null;
}
async launch(opt) {
/// #if process.env.BUILD_TARGET === 'node'
if (globalThis.process)
// For TS build
// this.worker = new Worker(require.resolve('@lcap/nasl-typescript-worker/src/index.js'));
this.worker = new worker_threads_1.Worker(require.resolve('../../ts-worker/src/index.js'));
/// #endif
/// #if process.env.BUILD_TARGET !== 'node'
if (globalThis.window) {
// const source = require('!!raw-loader!@lcap/nasl-typescript-worker/dist/index.js').default;
this.worker = await (async () => {
if (process.env.NODE_ENV === 'development') {
const source = require('!!raw-loader!../../ts-worker/bundle.js').default;
const url = URL.createObjectURL(new Blob([source]));
const worker = new window.Worker(url);
URL.revokeObjectURL(url);
return worker;
}
const instance = globalThis.window;
const baseIdePath = instance?.baseIdePath;
const hash = process.env.COMMIT_HASH || '';
const filename = instance?.electron?.createWorker
? 'tsserver.electron.js'
: 'tsserver.js';
// @ts-ignore
const link = `${baseIdePath}/js/${filename}?${hash}`;
const options = { name: 'typescript-server' };
return createWorker(link, options);
})();
}
/// #endif
this.http = opt.http;
this.isAnnotationMode = opt?.isAnnotationMode ?? false;
this.logger = opt.logger ?? utils.internalLogger;
this.useCache = opt.useCache ?? false;
this.diagnosticManager = new diagnostic_1.DiagnosticManager();
this.messager = new SentryMessager({
protocol: 'ts-worker',
sender: 'ide',
context: this,
timeout: 420000,
getReceiver: () => this.worker,
getSender: () => this.worker,
handleMessage: async ({ data }) => {
if (!data || data.event !== 'publishDiagnostics') {
return;
}
// getDiagnosticRecordsAndPushAll 最终通过 ts 回调走到这里
const tsDiagnostics = await this._resolveDiagnosticRecords(data.records, data.versions) ?? [];
if (!this.isAnnotationMode) {
// TODO wudengke 下面这次类型标注是错的,没有考虑versions
await utils.timeSlicingWithGenerator(this._incrementalAnnotationJSONWithGenerator(data.records));
if (this.isFirstScreen) {
// 首屏时,语言服务初始化
await this.languageServerInitiate();
}
// 已经和 NASL 节点配对的诊断信息
const isProfileMode = this.flags.profileMode;
if (this.isFirstScreen) {
utils.isDebugMode && console.time('\x1b[44m\x1b[97m 用户体感首屏检查耗时 \x1b[0m');
// 对OQL节点加载来自TS的错误
tsDiagnostics.forEach(({ node, message }) => {
if (node.errorsFromTSServer) {
node.errorsFromTSServer.push({ message });
}
else {
node.errorsFromTSServer = [{ message }];
}
});
if (isProfileMode) {
console.profile('check');
}
let fixAllUseBeforeAssignTasks = [];
const rawApp = NaslServer_1.toRaw(this.getProxyApp());
// 让事件逻辑显示出来
this._getAllElementLogicRoots();
try {
// 目前里面包含所有定义、逻辑、业务组件、前端全局变量等
fixAllUseBeforeAssignTasks = await (0, nasl_language_server_core_1.firstScreenCheckAndCompare)(this.semEnv, rawApp);
// 类型检查完才能收集引用
this.semEnv.refMgr.buildSymbolRefsForApp(this.semEnv, rawApp, undefined);
}
catch (err) {
this.semEnv.logger.fatal('\x1b[44m\x1b[97m 类型检查出错了,估计没标完 \x1b[0m');
this.semEnv.logger.fatal(err);
}
if (isProfileMode) {
console.profileEnd('check');
}
if (isProfileMode) {
console.profile('diag');
}
// Use performance measurement to accurately track time in time-sliced execution
const diagAppStartTime = performance.now();
await utils.runGeneratorAsync(this.semEnv.errorDiagnoser.diagnosticApp());
const diagAppEndTime = performance.now();
console.error(`\x1b[44m\x1b[97m Diagnostic App \x1b[0m: ${((diagAppEndTime - diagAppStartTime) / 1000).toFixed(3)}s`);
const diagnosticMap = this.semEnv.errorDiagnoser.getAllDiagnostics();
const transformStartTime = performance.now();
const records = transformDiagnosticMapToRecords(diagnosticMap);
await this.diagnosticManager.setInitialDiagData(records);
const transformEndTime = performance.now();
console.error(`\x1b[44m\x1b[97m Transform Diagnostic Map To Records \x1b[0m: ${((transformEndTime - transformStartTime) / 1000).toFixed(3)}s`);
// 首屏和增量通路不同,首屏时需要手动调用lsRunEndSwitch;增量时则在增量代码中调用
this._lsRunEndSwitch();
this.isFirstScreen = false;
rawApp.emit('collect:start', {
actionMsg: 'v3.14 v4.0 新 LS 修复变量先使用后赋值的场景'
});
// 🪝,放在前面的话有时序问题,容易多很多重复报错
fixAllUseBeforeAssignTasks.forEach(task => task());
rawApp.emit('collect:end');
this.semData.isFirstScreen = false;
this.semEnv.allocatedVEQNames.forEach(n => {
this.semEnv.refMgr.qNameDefs.delete(n);
});
this.semEnv.allocatedVEQNames.length = 0;
for (const value of this.cachedEventPayloadValues) {
this.embeddedTSEmitter.emit('change', { value });
}
this.cachedEventPayloadValues.length = 0;
if (isProfileMode) {
console.profileEnd('diag');
}
utils.isDebugMode && console.timeEnd('\x1b[44m\x1b[97m 用户体感首屏检查耗时 \x1b[0m');
}
(0, nasl_language_server_core_1.clearFileNodeCache)();
}
this.notifyPublishDiagnosticsEnd(tsDiagnostics);
if (this.firstScreenEndWork) {
this.firstScreenEndWork();
this.firstScreenEndWork = undefined;
}
},
});
// 监听所有改变操作
this.embeddedTSEmitter = new concepts_1.EventEmitter();
if (!this.isAnnotationMode) {
this.embeddedTSEmitter.on('change', ({ value: eventValue }) => {
if (this.isFirstScreen) {
this.cachedEventPayloadValues.push(eventValue);
return;
}
const itemEventPtr = new Array();
eventValue.forEach((item) => {
// 查找引用无法处理合并后的请求,需要拦截后缓存数据
item.originEvent && itemEventPtr.push({
action: item.originEvent.action,
objNasl: item.originEvent.objNasl,
oldObjNasl: item.originEvent.oldObjNasl,
field: item.originEvent.field, // 判断是否是 rename
target: item.originEvent.target, // rename 专用
ctxProcess: item.originEvent.ctxProcess // 流程特殊情况:更新流程关联的 view
});
/**
* 多个行为进行合并
* 合并规则是
* 1.[a,b,c,c] 在cc的时候,在已有一个c,又来一个c的时候,file级别的节点
* 就可以前面的内容去掉,只保留最后一个, 但是如果前面那个有携带file
* 2.[a,b,c,b,c] 在bc已存在,又来了bc 这就都要保留,因为可能顺序有相关性,不能去掉
* 理论上只处理同时几个操作 合并,
* 3.有field的也直接保留
*/
try {
const changeEvent = item.originEvent;
// 设置国际化相关内容传标识过来,不需要进入语言服务
if (changeEvent?.field === 'setI18n')
return;
const changeNode = changeEvent.target;
// FIXME: 验证完成后需要移除
const { fileNode: fileNode2 } = NaslServer_1.getCurrentSource(changeNode);
const fileNode = (0, service_1.getFileNode)(changeNode);
if (NaslServer_1.toRaw(fileNode) !== NaslServer_1.toRaw(fileNode2)) {
this.logger.error('\x1b[41m\x1b[97m ☁️ embeddedTSEmitter change 事件中计算不一致 \x1b[0m');
}
// 这个方法是 5.0 加入标准库的,但是这里 ts 版本是 4.x,ci 会挂,所以需要忽略
// @ts-ignore
const findLastIndex = this.changeStackList.findLastIndex((changeStackItem) => {
const { target } = changeStackItem;
const { fileNode: targetFileNode } = NaslServer_1.getCurrentSource(target);
return targetFileNode === fileNode;
});
// 如果当前找到了节点,而且节点在数组的最后一项,说明此次可以合并
if (findLastIndex !== -1 && findLastIndex === this.changeStackList.length - 1) {
// 最后一项有field就直接保留,新的也不塞,直接return
if (this.changeStackList[findLastIndex]?.field) {
// 如果当前列表里有,这个文件节点,最后一个是field的话,直接return掉,不用塞这个update了
return;
}
// 如果最后一项是普通的update的话,就可以去掉,后面那个会在塞过来
this.changeStackList.pop();
}
this.changeStackList.push(item.originEvent);
}
catch (err) {
this.logger.info(err);
if (item.originEvent) {
this.changeStackList.push(item.originEvent);
}
}
});
// 如果有长度,那么触发类型检查
if (this.changeStackList.length) {
try {
itemEventPtr.forEach(payload => {
// 增量更新 step 1:处理 create、delete、update 的局部符号
const startTime = performance.now();
this.semEnv.refMgr.updateRefsInNaslFragment(this.semEnv, payload);
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 10) {
console.log(`\x1b[44m\x1b[97m Reference manager: Update Nasl Fragment References took ${duration.toFixed(2)}ms \x1b[0m`);
}
});
}
catch (err) {
this.logger.error('error: update references in nasl fragment', err);
}
void this.flushChangesForTypeChecking().catch(err => {
this.logger.error('triggerTypeChecking error', err);
});
}
});
}
}
async createUiTs(components, options) {
// save resources so that it could be reused when importing any templates
// without ui ts data, renaming will be wrong, theoretically
if (this.isFirstScreen) {
this.createUiTsArg0Copy = components;
this.createUiTsArg1Copy = options;
}
let resolve = undefined;
this.inited = new Promise((finish) => {
resolve = finish;
});
const { standardUIComponents, // 标准组件(带有ts类型定义文件的组件)
} = options;
Object.assign(allComponent, components);
const { code, elementsLogic } = await (0, createUiTs_1.default)(allComponent);
// 处理一堆api.ts的类型
const convertTsCode = (code) => {
code = code || '';
code = code.replace(/nasl.core.Integer/g, 'nasl.core.Long');
// 匹配union类型的正则
const unionReg = /\s*['"].*['"](\s*\|\s*['"].*['"])+/g;
code = code.replace(unionReg, 'nasl.core.String');
return code;
};
// 放入生產的uits文件
await this.addFile({
file: 'nasl.ui.extra.ts',
fileContent: code,
}, { cache: true });
// 添加基础组件的方法到elementsLogic
Object.assign(elementsLogic, (0, createUiTs_1.createEleLogins)(standardUIComponents));
const naslUiComponentTsFile = {
file: 'nasl.ui.component.ts',
fileContent: convertTsCode(options.basicUITsCode),
};
// 基础组件
await this.addFile(naslUiComponentTsFile, { cache: true });
this._debugPutFilesInAnonymousApp([naslUiComponentTsFile]);
const extensionComponentFile = {
file: 'extension.component.ts',
fileContent: convertTsCode(options.withTypeLibraryTsCode),
};
// 带有类型的依赖库组件
await this.addFile(extensionComponentFile, { cache: true });
this._debugPutFilesInAnonymousApp([extensionComponentFile]);
resolve(() => {
(0, nasl_language_server_core_1.parseTsFnToNaslQNameRefMaps)(this.semEnv.refMgr, naslUiComponentTsFile.fileContent);
if (extensionComponentFile.fileContent) {
(0, nasl_language_server_core_1.parseTsFnToNaslQNameRefMaps)(this.semEnv.refMgr, extensionComponentFile.fileContent);
}
// allComponent中包含一些定义独此一份的legacy扩展组件,因此需要加载
Object.values(allComponent).forEach((v) => {
this.semEnv.refMgr.compDefMgr.addLegacy(v, 'allComponent legacy');
});
const allNodesAPI = (0, concepts_1.getConfig)().allNodesAPI;
Object.keys(allNodesAPI).forEach((tag) => {
const component = allNodesAPI[tag];
// @ts-expect-error FIXME wudengke
this.semEnv.refMgr.compDefMgr.add(component, 'from allNodesAPI');
});
});
// 内置标准库类型
Object.keys(naslStdlibMap_1.default).forEach(async (libFileName) => {
await this.addFile({
file: `/${libFileName}`,
fileContent: naslStdlibMap_1.default[libFileName],
}, { cache: true });
});
this.elementsLogic = elementsLogic;
}
*contentToFile(module1) {
// @ts-ignore
const module = module1.__v_raw ?? module1;
if (!module) {
return [];
}
const self = this;
const results = [];
const getTsFile = function* getTsFile(node, name, pre) {
const isContinue = pre ? (input) => input instanceof pre : () => true;
try {
if (!isContinue(node)) {
return;
}
const result = yield* node.toEmbeddedTSFile();
results.push(result);
node.sourceMap = result.sourceMap;
self.file2NodeMap.set(result.filePath, node);
}
catch (err) {
if (utils.isDebugMode) {
self.logger.warn(node.nodePath ? node.nodePath : name, '代码转换失败', err);
}
if (self.isAnnotationMode) {
throw err;
}
}
};
const getTsFiles = function* getTsFiles(nodes, name, pre) {
for (const node of nodes ?? []) {
yield* getTsFile(node, name, pre);
}
};
const concat = (arr, key) => {
return arr.reduce((ans, item) => ans.concat(item[key]), []);
};
// 禁用的依赖库不生成代码 区分 undefined 和 false
if (module instanceof concepts_1.Module && module.type === 'extension' && module.enable === false) {
return [];
}
const { structures = [], metadataTypes = [], interfaces = [], enums = [], logics = [], authLogics = [], authLogicsForCallInterface = [], processes = [], processV2s = [], dataSources = [], triggerLaunchers = [], connections = [], roles = [], overriddenLogics = [], backend, configuration, preferenceMap = {}, } = module;
const { namespaces = [], triggers = [], dependencies = [] } = module;
// TODO wudengke 删除
const { frontends = [] } = module;
yield* getTsFiles(structures, 'structure');
// 启用元数据时才翻译
if ((module.concept === 'App' && String(preferenceMap.metadataTypeEnable) === 'true') ||
((module.concept === 'Module' && module.type === 'sharedApp'))) {
yield* getTsFiles(metadataTypes, 'metadataType');
}
if (module instanceof concepts_1.App) {
yield* getTsFiles(overriddenLogics, 'overriddenLogic', concepts_1.OverriddenLogic);
yield* getTsFiles(backend?.variables ?? [], 'backend_variable');
// TODO wudengke 删除
// 都不生成了,调试时自己加回来
if (utils.isDebugMode) {
// for (const frontendType of (frontendTypes || [])) {
// yield* getTsFile(frontendType, 'frontendType');
// for (const dep of (frontendType.componentDependencies || [])) {
// results.push(...(yield* self.contentToFile(dep)));
// }
// for (const frontend of frontendType.frontends) {
// yield* getTsFile(frontend, 'frontend');
// yield* getTsFiles(frontend.variables, 'frontend_variable');
// yield* getTsFiles(frontend.bindEvents, 'frontend_bindEvent_logic');
// // 如果开了极速开发模式,就不生成view的代码
// if (!config.closeViews && !this.performance) {
// yield* view2TSFile(frontend.views);
// }
// }
// // 业务组件
// yield* getTsFiles(frontendType.businessComponents, 'businessComponents');
}
}
else {
// 依赖库
// utils.isDebugMode && yield* getTsFiles(concat(frontends, 'logics' as any), 'frontend_logic');
}
yield* getTsFiles(dataSources, 'dataSource');
yield* getTsFiles(concat(dataSources, 'entities'), 'dataSource_entity');
yield* getTsFiles(interfaces, 'interface');
yield* getTsFiles(enums, 'enum');
yield* getTsFiles(logics, 'logic');
yield* getTsFiles(authLogics, 'authLogic', concepts_1.Logic);
yield* getTsFiles(authLogicsForCallInterface, 'authLogicForCallInterface', concepts_1.Logic);
yield* getTsFiles(processes, 'process');
yield* getTsFiles(processV2s, 'processV2');
if (module instanceof concepts_1.Connector) {
yield* getTsFiles(triggerLaunchers, 'triggerLauncher');
// yield* getTsFiles(authLogicsForCallInterface, 'authLogicForCallInterface');
yield* getTsFiles(concat(namespaces, 'logics'), 'namespaces_logic');
yield* getTsFiles(triggers, 'connectorTrigger');
yield* getTsFiles(dependencies, 'dependencies');
}
if (module instanceof concepts_1.Module && module.type === 'sharedApp') {
yield* getTsFile(module, 'sharedApp');
}
if (module instanceof concepts_1.App) {
yield* getTsFiles(roles, 'role');
yield* getTsFiles(connections, 'connection');
yield* getTsFiles(triggerLaunchers, 'triggerLauncher');
}
for (const group of configuration?.groups ?? []) {
const transformGroupName = ['custom', 'auth', 'fileStorage', 'system'];
if (transformGroupName.includes(group.name)) {
yield* getTsFiles(group?.properties ?? [], 'configuration_group_property');
}
}
return results;
}
async requestDatabaseConfigs(http, app) {
let data = [];
if (this.useCache) {
data = JSON.parse(JSON.stringify(sqlCategory_json_1.default));
}
else {
const res = await http.get('/api/v1/app/config/list?category=sql');
data = res.data?.result || [];
}
const map = data.filter((item) => item.category === 'sql' && item.available)
?.reduce((acc, item) => ({
...acc,
[item.configId]: item.configs?.type,
}), ({}));
app.saveDatabaseConfigIdToTypeMap(map);
}
async requestSqlFunctions(http, appName) {
let data = [];
if (this.useCache) {
data = JSON.parse(JSON.stringify(sqlFunctions_json_1.default));
}
else {
const res = await http.get('/api/v1/app/config/database/functions');
data = res.data?.result?.result || [];
}
const files = data
.filter((item) => !!item.signature)
.map((item) => {
return {
file: `/embedded/${appName}/sqlFunctions/${item.dbType}.ts`,
fileContent: item.signature.replace('declare namespace nasl.util', `declare namespace nasl.sqlFunction.${item.dbType}`),
};
});
data.forEach((item) => {
delete item.signature;
});
this._sqlFunctions = data;
return files;
}
getSqlFunctions(dbType) {
const res = this._sqlFunctions.find((f) => f.dbType === dbType);
if (res) {
res.functions.forEach((func) => {
func.expanded = true;
});
}
return res;
}
getSqlFunction(dbType, functionName) {
if (!functionName)
return null;
const sqlFunction = this.getSqlFunctions(dbType);
if (!sqlFunction)
return null;
const upperFunctionName = functionName.toUpperCase();
for (const category of sqlFunction.functions) {
for (const func of category.children) {
if (func.name === upperFunctionName) {
return func;
}
}
}
return null;
}
async requestLatestVersionsOfSharedApp(app) {
const params = app.sharedAppDependencies?.map((item) => ({
name: item.name,
provider: item.provider,
version: item.version,
}));
// const res = await this.http.post('/api/v1/share/center/imported/changed/list', params);
// this._latestVersionsOfSharedApp = res.data;
// this.semEnv?.errorDiagnoser?.setLatestVersionsOfSharedApp(res.data);
}
getStatusOfSharedApp(sharedApp) {
const version = this._latestVersionsOfSharedApp.find((item) => item.name === sharedApp.name && item.provider === sharedApp.provider);
if (!version || !version.version)
return 'delete';
else if (version.version !== sharedApp.version)
return 'update';
}
sharedAppsUpdated(app) {
return app.sharedAppDependencies?.some((item) => !!this.getStatusOfSharedApp(item));
}
async checkStatusOfSharedApps(app) {
const statusMap = new Map();
app.sharedAppDependencies?.forEach((item) => {
const key = `${item.name}-${item.provider}`;
statusMap.set(key, this.getStatusOfSharedApp(item));
});
await this.requestLatestVersionsOfSharedApp(app);
const changedSharedApps = [];
app.sharedAppDependencies?.forEach((item) => {
const key = `${item.name}-${item.provider}`;
if (statusMap.get(key) !== this.getStatusOfSharedApp(item))
changedSharedApps.push(item);
});
this.embeddedTSEmitter.emit('change', {
value: changedSharedApps.map((item) => ({
originEvent: {
action: 'create',
target: item,
},
})),
});
}
getLatestVersionsOfSharedApp() {
return this._latestVersionsOfSharedApp;
}
getUpdatedSharedApp(app) {
return app.sharedAppDependencies?.find((sharedApp) => !!this.getStatusOfSharedApp(sharedApp));
}
async fetchDtsFile(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch file: ${response.statusText}`);
}
return await response.text();
}
async openApp(app, performance = false, needRegisterCommand = true) {
app.naslServer = this;
this.isFirstScreen = true;
Object.defineProperty(this, 'getProxyApp', {
value: () => app,
configurable: true,
});
const rawApp = NaslServer_1.toRaw(app);
this.lsRunEnd = new Promise((resolve) => {
this._lsRunEndSwitch = resolve;
});
if (utils.isAutoTestEnv) {
this.logger.info('运行在自动化测试模式下...');
}
// 初始化 App 配置
await (0, initial_1.initialize)({ app, axios: this.http, useCache: this.useCache });
this.logger.time('全量生成并写入 TS 文件——总计');
const toEmbeddedTSStartTime = new Date().getTime();
this.logger.time('全量生成 TS——翻译');
this.logger.time('全量生成 TS——翻译——简单语义分析');
this.semData = new nasl_concepts_1.SemanticData();
// 除此之外,好像没什么简单的方法可以让 __xxx.ts 文件快速拿到 app.naslServer 的引用了……
// @ts-ignore
this.semData.collectAllSemanticCtx(rawApp);
// @ts-ignore
this.semData.redirectSlowGetters(rawApp);
this.logger.timeEnd('全量生成 TS——翻译——简单语义分析');
const { notifyEndWork } = this.notifyStartWork();
this.firstScreenEndWork = () => {
this.logger.info('firstScreenEndWork');
notifyEndWork();
};
const self = this;
try {
await this.requestDatabaseConfigs(this.http, app);
}
catch (err) {
this.logger.error('请求数据库配置列表失败', err);
}
let sqlSignatureFiles = [];
try {
this.sqlSignatureFiles = await this.requestSqlFunctions(this.http, app.name);
}
catch (err) {
this.logger.error('请求sql函数列表失败', err);
}
// 等待 sql 查询
await this.waitOqlQueryComponentChildrenFinish(app);
// 翻译 ts 文件
const results = await utils.runGeneratorAsync(getAllTsFiles());
this.semData.recoverSlowGetters();
this.semData.clearSemanticData();
const files = results.map((result) => ({
file: result.filePath,
fileContent: result.code,
}));
this.logger.timeEnd('全量生成 TS——翻译');
this.logger.time('全量生成 TS——写入文件');
// 全量生成TS文件,因此清空版本记录
files.forEach((f) => {
this.diagnosticManager.deleteVersionByFilePath(f.file);
});
// 添加 sql 函数签名文件
this.sqlSignatureFiles.forEach((sqlFunction) => {
files.push(NaslServer_1.toRaw(sqlFunction));
});
await this.writeFiles(files);
this._debugInFileStorage(app, files);
if (needRegisterCommand) {
// 修改名称回调
(0, common_1.unregisterCommand)('tsRename.change');
(0, common_1.registerCommand)('tsRename.change', (value, callback, toast) => {
this.currentRenameMode = value ? 'update' : 'rename-only';
callback(value, toast);
});
// 删除回调
(0, common_1.unregisterCommand)('tsDelete.change');
(0, common_1.registerCommand)('tsDelete.change', (callback) => {
callback();
});
}
this.logger.timeEnd('全量生成 TS——写入文件');
this.logger.timeEnd('全量生成并写入 TS 文件——总计');
const toEmbeddedTSEndTime = new Date().getTime();
const toEmbeddedTSUsedTime = toEmbeddedTSEndTime - toEmbeddedTSStartTime;
app.naslServer.appPerfermenceData = app.naslServer.appPerfermenceData || {};
app.naslServer.appPerfermenceData.toEmbeddedTSUsedTime = toEmbeddedTSUsedTime;
function* getAllTsFiles() {
const files = yield* self.contentToFile(app);
const otherModules = [
...app.integration?.connectors ?? [],
...app.dependencies ?? [],
...app.connectorDependencies ?? [],
...app.interfaceDependencies ?? [],
...(app.sharedAppDependencies ?? []),
];
for (const item of otherModules) {
files.push(...yield* self.contentToFile(item));
}
const stdNamespace = (0, concepts_1.getStdlibNamespace)();
// 部分namespace不进行hardcode,而是通过代码生成的方式在运行时注入
const errorNamespace = stdNamespace.findChild("error");
files.push(yield* errorNamespace.toEmbeddedTSFile());
return files;
}
}
async refreshApp(app, performance) {
// 清除所有语义信息
this.semEnv.clearAll();
// 清除所有问题
this.diagnosticManager.clear();
// 刷新要清除文件列表
this.file2NodeMap.clear();
// 刷新 ts 代码
this.tsFiles.clear();
await this.deleteDirectoryFiles({ directoryName: '/embedded' });
// 清楚check count的数量
await this.messager.requestCommand('_clearTimeout');
// 如果没传就用当前this的
if (performance === undefined) {
performance = this.performance;
}
// 重新加载app下内容
await this.openApp(app, performance);
// 重新check一遍所有内容
await this.getDiagnosticRecordsAndPushAll();
// check内容后,会自动走增量类型标注完善全部类型
}
/**
* 初始化之前添加文件
*/
addFile(file, { cache = false } = {}) {
cache && this.cacheFile(file);
this.tsFiles.set(file.file, file.fileContent);
return this.messager.requestCommand('addFile', file);
}
// 缓存添加过的文件
cacheFile(options) {
__naslStdlibFileCacheMap.set(options.file, options);
}
/**
* 只新增文件
* @param {*} files
*/
writeFiles(files) {
files.forEach(({ file, fileContent }) => this.tsFiles.set(file, fileContent));
return this.messager.requestCommand('writeFiles', files);
}
/**
* 新增、修改
* 删除文件 文件用修改内容为空模拟,防止报错
* @param {*} args
*/
updateFiles(args) {
args.outputFiles.forEach(({ file, fileContent }) => this.tsFiles.set(file, fileContent));
return this.messager.requestCommand('updateFiles', args);
}
/**
* 清除一个目录下的所有文件
*/
deleteDirectoryFiles(args) {
Array.from(this.tsFiles.keys())
.filter((key) => key.startsWith(args.directoryName))
.forEach((key) => this.tsFiles.delete(key));
return this.messager.requestCommand('deleteDirectoryFiles', args);
}
async _debugPutFilesInAnonymousApp(files) {
return this._debugPutFilesByAppId('anonymous', files);
}
async _debugPutFilesByAppId(appId, openFiles) {
// 测试模式下跳过调试步骤
if (process.env.NODE_ENV === 'testing' || !this.openDebugEmbedded) {
this.openDebugEmbedded = false;
return;
}
const url = `/proxy/nasl-storage/api/App/debugEmbedded?id=${appId}`;
/// #if process.env.NODE_ENV === 'development'
// 首次尝试请求
await this.http.post(url, openFiles[0]).catch(() => {
this.openDebugEmbedded = false;
});
if (this.openDebugEmbedded && globalThis.window) {
// For TS build
try {
let canDebug = true;
await this.http.post(url, openFiles[0]).catch(() => (canDebug = false));
if (canDebug) {
await Promise.all(openFiles.map(async (file) => {
const res = await this.http.post(url, file);
return res.data;
}));
}
}
catch (e) {
// ..
}
}
/// #endif
/// #if process.env.BUILD_TARGET === 'node'
if (globalThis.process && process.env.NODE_ENV === 'development') {
// For TS build
try {
await Promise.all(openFiles.map(async (file) => fs.outputFile(path.join(__dirname, '../debug/apps', appId, file.file), file.fileContent)));
}
catch (e) {
if (utils.isDebugMode) {
this.logger.error(e);
}
}
}
/// #endif
}
async _debugInFileStorage(node, openFiles) {
let app = node;
if (node.concept !== 'App') {
app = node.rootNode || node.app;
}
return this._debugPutFilesByAppId(app.id, openFiles);
}
open() {
return this.messager.requestCommand('open');
}
updateOpen(args) {
return this.messager.requestCommand('updateOpen', args);
}
fileReferences(filePath) {
return this.messager.requestCommand('fileReferences', filePath);
}
async references(args) {
return (await this.messager.requestCommand('references', args))?.response;
}
getValueSelectCompletion(node, value, noFilterList) {
const { currentSource, fileNode } = NaslServer_1.getCurrentSource(node);
// this.logger.info(currentSource, fileNode);
if (currentSource && fileNode) {
return this._getValueSelectCompletion({
file: fileNode.getEmbeddedFilePath(),
range: {
line: (0, translator_1.lsp2tspNumber)(currentSource.start.line),
offset: (0, translator_1.lsp2tspNumber)(currentSource.start.character),
},
value,
noFilterList,
});
}
this.logger.info('没找到节点', node, currentSource, fileNode);
}
_getValueSelectCompletion(args) {
return this.messager.requestCommand('getValueSelectCompletion', args);
}
async getDataSchemaStructureOrTypeAnnotation(node) {
if (!(node instanceof concepts_1.ViewElement))
return;
const fileNode = node.likeComponent;
const sourceMap = fileNode.sourceMap;
if (!fileNode.sourceMap) {
return;
}
let currentSource;
for (const item of sourceMap) {
const [key, v] = item;
// @ts-ignore
if ((key.__v_raw || key) === (node.__v_raw || node)) {
currentSource = v;
break;
}
}
// const { currentSource, fileNode } = NaslServer.getCurrentSource(node);
if (!currentSource) {
return;
}
const quickInfo = await this._getTypeQuickinfo({
file: fileNode.getEmbeddedFilePath(),
line: (0, translator_1.lsp2tspNumber)(currentSource.start.line),
offset: (0, translator_1.lsp2tspNumber)(currentSource.start.character) + `new nasl.ui.`.length,
});
if (quickInfo.responseRequired) {
const displayString = quickInfo?.response?.displayString || '';
const flag = displayString.includes('<') && displayString.includes('>');
if (!flag)
return;
const types = /<([^()]+)>/g.exec(displayString);
const typeStr = types && types[1];
const app = node.getAncestor('App');
if (typeStr.includes('__name: "AStructure_')) {
const properties = [];
typeStr.replace(/([^:\s]+):\s+([^;]+);/g, ($1, name, typeKey) => {
if (name === '__name')
return;
typeKey = `app.${typeKey}`;
const keys = typeKey.split('.');
const typeName = keys.pop();
const typeNamespace = keys.join('.');
properties.push(concepts_1.StructureProperty.createProtoTypeOnly({
name,
typeAnnotation: concepts_1.TypeAnnotation.createReferenceProtoTypeOnly({
typeName,
typeNamespace,
}),
}));
return '';
});
return concepts_1.TypeAnnotation.createTypeAnonymousStructure(properties);
}
if (typeStr.startsWith('structures'))
return app.findNodeByCompleteName(`app.${typeStr}`);
}
}
/**
* ts的 quickInfo方法,查询指定位置的详情
* @param args 文件信息数组
* @returns 查询到的
*/
_getTypeQuickinfo(args) {
return this.messager.requestCommand('quickInfo', args);
}
_getQuickInfoFull(args) {
return this.messager.requestCommand('quickInfoFull', args);
}
/**
* 获取nasl节点的 指定位置的详情
* 而且可以返回正确类型
* @param args 文件信息数组
* @returns 查询到的
*/
async getNaslNodeQuickInfoFull(args) {
const result = await this._getQuickInfoFull(args);
return result;
}
_getTypeFull(args) {
return this.messager.requestCommand('typeFull', args);
}
_getTypeBatch(args) {
return this.messager.requestCommand('typeBatch', args);
}
async getNaslNodeTypeFull(args) {
const result = await this._getTypeFull(args);
return result;
}
async getNaslNodeTypeBatch(args) {
const result = await this._getTypeBatch(a