UNPKG

@lcap/nasl

Version:

NetEase Application Specific Language

1,163 lines 180 kB
"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