@lcap/nasl
Version:
NetEase Application Specific Language
649 lines (628 loc) • 22.9 kB
JavaScript
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;