UNPKG

@lcap/nasl

Version:

NetEase Application Specific Language

649 lines (628 loc) 22.9 kB
const start = require('./start').default; const Messager = require('../lib/Messager').default; let connectionPort; if (typeof self === 'undefined') { global.self = global; connectionPort = require('worker_threads').parentPort; } else { connectionPort = globalThis; } // connectionPort.addEventListener = (eventType, listener) => { // connectionPort.addListener(eventType, (data) => listener({ data })); // }; const messager = new Messager({ protocol: 'ts-worker', sender: 'worker', context: this, getReceiver: () => connectionPort, getSender: () => connectionPort, }); let seq = 1; /** * 需要收集 message 做统一处理 */ const webHost = { diagnosticRecords: [], completionEntries: [], readFile: (data) => globalThis.context.system.readFile(data), fileExists: (data) => globalThis.context.system.fileExists(data), writeMessage(data) { /* eslint-disable no-console */ if (console[data.level]) { // console[data.level](data.body); } else { // if (data.type !== 'event') { // console.log(data); // } const { event, body, command, success } = data; if (event === 'syntaxDiag' || event === 'semanticDiag' || event === 'suggestionDiag') { let record; const lastRecord = this.diagnosticRecords[this.diagnosticRecords.length - 1]; if (lastRecord && lastRecord.filePath === body.file) { record = lastRecord; } else { record = { filePath: body.file, syntaxDiagnostics: [], semanticDiagnostics: [], suggestionDiagnostics: [], }; this.diagnosticRecords.push(record); } record[event + 'nostics'] = body.diagnostics; } if (command === 'completionInfo') { if (success === true) { this.completionEntries = body.entries || []; } else { this.completionEntries = []; } } } }, }; function getRange(content) { const range = { start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 }, }; const arr = content.split('\n'); range.end.line = arr.length; range.end.offset = arr[arr.length - 1].length || 1; return range; } const context = { messager, system: undefined, session: undefined, async start() { Object.assign(this, await start(webHost)); }, /** * 与 system.fsMap 同步 * @param {*} args * @returns */ updateOpen(args) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'updateOpen', arguments: args, }); }, /** * 修改内容方法 * @param {*} path 文件路径 * @param {*} updateParams 要修改的内容 */ actionOneFileChange(path, updateParams) { this.session.executeCommand({ seq: seq++, type: 'request', command: 'updateOpen', arguments: { openFiles: [], changedFiles: [ { fileName: path, textChanges: updateParams, }, ], closedFiles: [], }, }); }, /** * @param {*} file, 文件名 range 位置信息, options 其他参数 * @returns 查找到后的联想内容 */ getCompletionInfo({ file, range, options }) { this.session.onMessage({ seq: seq++, type: 'request', command: 'completionInfo', arguments: { file, includeExternalModuleExports: true, includeInsertTextCompletions: true, line: range.line, offset: range.offset, ...options, }, }); return webHost.completionEntries; }, // 在初始化之前添加文件 addFile(file) { this.system.writeFile(file.file, file.fileContent); }, /** * 只新增文件 * @param {*} files */ writeFiles(files) { files.forEach((file) => this.system.writeFile(file.file, file.fileContent)); return this.session.executeCommand({ seq: seq++, type: 'request', command: 'updateOpen', arguments: { openFiles: files, }, }); }, /** * 新增、修改或删除文件 * @param {*} files */ updateFiles({ outputFiles }) { const args = { openFiles: [], changedFiles: [], closedFiles: [], }; outputFiles.forEach((file) => { if (this.system?.fileExists(file.file)) { const oldFileContent = this.system.readFile(file.file); args.changedFiles.push({ fileName: file.file, textChanges: [ Object.assign(getRange(oldFileContent), { newText: file.fileContent, }), ], }); this.system.writeFile(file.file, file.fileContent); } else { this.system.writeFile(file.file, file.fileContent); args.openFiles.push(file); } }); // 使用修改内容为空代表删除 // closedFiles.forEach((filePath) => { // globalThis.context.system.deleteFile(filePath); // }); // console.log(args, 'argsargsargs'); this.session.executeCommand({ seq: seq++, type: 'request', command: 'updateOpen', arguments: args, }); }, // 删除指定目录下的文件 deleteDirectoryFiles(options) { const { directoryName = '/embedded' } = options || {}; const args = { openFiles: [], changedFiles: [], closedFiles: [], }; const closedFiles = this.system.readDirectory(directoryName); closedFiles.forEach((filePath) => { this.system.deleteFile(filePath); }); args.closedFiles = closedFiles; this.session.executeCommand({ seq: seq++, type: 'request', command: 'updateOpen', arguments: args, }); }, _clearTimeout() { const count = this.system.timeoutCallbacks.count(); count && this.system.checkTimeoutQueueLengthAndRun(count); }, _reduceDiagnosticQueue(count) { try { for (let i = 0; i < count; i++) { this.system.checkTimeoutQueueLengthAndRun(1); this.system.runQueuedImmediateCallbacks(1); this.system.runQueuedImmediateCallbacks(1); } } catch (e) { if (e.operator === 'strictEqual') console.error(e); } }, publishDiagnostics(remain, versions) { webHost.diagnosticRecords = []; for (let i = 0; i < remain; i++) { try { this.system.checkTimeoutQueueLengthAndRun(1); this.system.runQueuedImmediateCallbacks(1); this.system.runQueuedImmediateCallbacks(1); } catch (e) { if (e.operator === 'strictEqual') { console.warn(e); } } } this.messager.sendData({ event: 'publishDiagnostics', records: webHost.diagnosticRecords, versions }); }, geterr(files = []) { // setTimeout(() => { // count++; // }, 10) }, getDiagnosticRecords(filePaths = this.system.readDirectory('/embedded'), versions = []) { this._clearTimeout(); this.session.executeCommand({ seq: seq++, type: 'request', command: 'geterr', arguments: { files: filePaths, delay: 0, }, }); webHost.diagnosticRecords = []; // this._reduceDiagnosticQueue(filePaths.length); // return webHost.diagnosticRecords; this.publishDiagnostics(filePaths.length, versions); return []; }, fileReferences(filePath) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'fileReferences', arguments: { file: filePath, }, }); }, references(args) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'references', arguments: args, }); }, // 查找内容信息 quickInfo(args) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'quickinfo', arguments: args, }); }, // 查找内容信息 quickInfoFull(args) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'quickinfo-full', arguments: args, }); }, typeBatch(args) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'type-batch', arguments: args, }); }, typeFull(args) { return this.session.executeCommand({ seq: seq++, type: 'request', command: 'type-full', arguments: args, }); }, getValueSelectCompletion({ file, range, value, noFilterList }) { if (!value) return []; const values = value.split('.'); const completionList = this.getCompletionInfo({ file, range }); const valueList = this.formatCompletions(completionList, { file, range }, { noDown: false, values, noFilterList }); return valueList; }, // 点击下拉框时候,获取指定属性的可以输入的属性,也就是先拼接某个属性取他的子集 getFieldKeySelectCompletion({ file, range, getFieldKey, noFilterList }) { // 传递过value值然后获取子集 let result = []; // 如果有value就获取他下一级的内容 if (getFieldKey) { result = this.changeValueAndGet({ file, range, value: getFieldKey }, true); } else { // 如果没有就获取当前这个位置的可选内容并且只查当前这层 const completionList = this.getCompletionInfo({ file, range }); result = this.formatCompletions(completionList, { file, range }, { noDown: true, noFilterList }); } return result; }, getSlotCurrentCompletion({ file, range }) { let result = []; // 获取当前这个位置的可选内容并且只查当前这层 const completionList = this.getCompletionInfo({ file, range }); result = this.formatSlotCurrentCompletion(completionList, { file, range }, { noDown: true }); return result; }, formatSlotCurrentCompletion(completions, { file, range }, methodConfig) { const { noDown } = methodConfig; const completionList = completions.filter((item) => item.sortText === '11' && item.name.startsWith('current')); const entryNames = []; const valueList = []; // 遍历取出所有的名称 completionList.forEach((item) => { entryNames.push(item.name); valueList.push({ text: item.name, }); }); // 找到有子集的对象 const details = this.completionEntryDetails({ file, range, entryNames }, noDown); valueList.forEach((item) => { if (details[item.text]) { item.children = details[item.text].children; item.completionDetail = details[item.text].completionDetail; } }); return valueList; }, /** * @param {*} completions 查到的依赖 * @param {*} { file, range } 文件和位置信息 * @param {*} noDown 是不是只取一层 * @param {*} values 继续向下查的数组 * @returns */ formatCompletions(completions, { file, range }, methodConfig) { const { noDown, values, noFilterList } = methodConfig; const completionList = completions.filter((item) => { // 过滤别的文件的, 而且过滤arguments 和 导出的,和不要函数 // 先过滤掉app // 以__开头的都屏蔽掉 // item.kindModifiers !== 'declare' 因为原生方法都是declare关键字的。 // 所以自己声明的时候尽量不要 用 declare给属性 // 因为使用Namespaces 每个点都算一个export,在第一次获取的时候,先过滤掉,接下来在放开 const defaultValue = item.sortText === '11' && item.name !== 'arguments' && item.kind !== 'function' && item.kind !== 'local function' && item.kind !== 'module' && item.kind !== 'method' && item.kind !== 'class' && item.name !== 'app' && item.name !== 'accept' && !item.name.startsWith('__'); if (!defaultValue) { // 确定要获取的,就也返回回去 if (noFilterList && Array.isArray(noFilterList)) { const find = noFilterList.find((noFilterItem) => item === noFilterItem.name); if (find) { return true; } } } return defaultValue; }); const entryNames = []; const valueList = []; // 遍历取出所有的名称 completionList.forEach((item) => { entryNames.push(item.name); valueList.push({ text: item.name, }); }); // 找到有子集的对象 const details = this.completionEntryDetails({ file, range, entryNames }, noDown, values); valueList.forEach((item) => { if (details[item.text]) { item.children = details[item.text].children; item.completionDetail = details[item.text].completionDetail; } }); return valueList; }, // 获取展开右侧的详情 completionEntryDetails({ file, range, entryNames }, noDown, values) { const res = this.session.executeCommand({ seq: seq++, type: 'request', command: 'completionEntryDetails', arguments: { file, line: range.line, entryNames, offset: range.offset, }, }); const response = res.response; const detailsMap = {}; response.forEach((item) => { let typeItem; if (item.displayParts) { // 是联合类型 if (item.displayParts.find((item) => item.text === '|' && item.kind === 'punctuation')) { // union类型就需要放入内容查出具体类型 const quickInfo = this.changeValueAndGetUnionCurrentType({ file, range, value: item.name + ' ' }); if (quickInfo.responseRequired && quickInfo.response?.length) { detailsMap[item.name] = { completionDetail: { nodeType: quickInfo.response[0]?.nodeType } }; return; } } else if (item.name.startsWith('current') || item.displayParts.find((item) => item.text === '{' && item.kind === 'punctuation')) { // 针对current或者类型里带了大括号的,都去去一下类型,要不字符串解析不出来 const quickInfo = this.changeValueAndGetUnionCurrentType({ file, range, value: item.name + ' ' }); if (quickInfo.responseRequired && quickInfo.response?.length) { detailsMap[item.name] = { completionDetail: { ...item, newNodeType: quickInfo.response[0]?.nodeType } }; return; } else { detailsMap[item.name] = { completionDetail: item, children: [{}] }; } } typeItem = item.displayParts[item.displayParts.length - 1]; } // if (typeItem.kind === 'punctuation' && typeItem.text === ']' || typeItem.kind === 'punctuation' && typeItem.text === '>') { // // 数组类型 // detailsMap[item.name] = { children: [{ text: 'length' }], completionDetail: item }; // } // if (typeItem.kind === 'punctuation' && typeItem.text === '}') { // // 对象类型,对象类型可以拿到当前对象的属性,先不动 // // 能拿到初始对象时候的属性的类型 // } const basicType = ['Integer', 'Long', 'Double', 'Decimal', 'String', 'Date', 'Time', 'DateTime', 'Email', 'Boolean', 'Any', 'stringLiteral', 'Text']; if (basicType.includes(typeItem.text)) { // 基础类型 detailsMap[item.name] = { completionDetail: item }; } else { // 只取一层,或者没有values // values就剩一个了,就剩一个就说明他当前就在上面的集合里来,就先停止,不用向下查了 if (noDown || !values || values.length <= 1) { // 如果是复杂类型标注出来, 不深入递归进去 // 塞入一个空的内容 detailsMap[item.name] = { completionDetail: item, children: [{}] }; } else { // 如果找到了就继续找 if (values[0] === item.name) { const value = values.shift(); // 复杂类型 ,传递name进去 查看是不是有属性可以.出来, detailsMap[item.name] = { completionDetail: item, children: this.changeValueAndGet({ file, range, value }, noDown, values), }; } else { // 如果别的值传递过来在当前层级已经找不到了,就先返回复杂类型 detailsMap[item.name] = { completionDetail: item, children: [{}] }; } } } }); return detailsMap; }, changeValueAndGet({ file, range, value }, noDown, values) { let result; if (value) { // 先带入当前值然后才可以获取.中的值 // 塞入当前value this.actionOneFileChange(file, [ { start: range, newText: value, end: range, }, ]); } // 获取.中的值 const getCompletion = this.changeGetCompletion({ file, range: { line: range.line, offset: range.offset + value.length, }, }); if (getCompletion.length) { // 先加一个点再去递归 this.actionOneFileChange(file, [ { start: { line: range.line, offset: range.offset + value.length, }, newText: '.', end: { line: range.line, offset: range.offset + value.length, }, }, ]); result = this.formatCompletions(getCompletion, { file, range: { line: range.line, offset: range.offset + value.length + 1, }, }, { noDown, values }); // 干掉递归的. this.actionOneFileChange(file, [ { start: { line: range.line, offset: range.offset + value.length }, newText: '', end: { line: range.line, offset: range.offset + value.length + 1 }, }, ]); } if (value) { // 把现在的值还原 this.actionOneFileChange(file, [ { start: range, newText: '', end: { line: range.line, offset: range.offset + value.length }, }, ]); } return result; }, // 增加当前的属性然后·出下面的属性 changeValueAndGetUnionCurrentType({ file, range, value }) { // 塞入当前value this.actionOneFileChange(file, [ { start: range, newText: value, end: range, }, ]); // 获取.中的值 const quickInfo = this.typeBatch([{ file, line: range.line, offset: range.offset, }]); // 把现在的值还原 this.actionOneFileChange(file, [ { start: range, newText: '', end: { line: range.line, offset: range.offset + value.length }, }, ]); return quickInfo; }, // 获取.中的内容 changeGetCompletion({ file, range }) { this.actionOneFileChange(file, [ { start: range, newText: '.', end: range, }, ]); // 通过.获取内容 const completionList = this.getCompletionInfo({ file, range: { line: range.line, offset: range.offset + 1, }, options: { triggerCharacter: '.', triggerKind: 2, }, }); this.actionOneFileChange(file, [ { start: range, newText: '', end: { line: range.line, offset: range.offset + 1 }, }, ]); return completionList; }, }; context.messager.options.context = context; exports.default = context;