完成trigger的扫描

This commit is contained in:
pqcqaq 2024-10-24 11:20:54 +08:00
parent f8440493d7
commit 571ab5b8d9
4 changed files with 405 additions and 5 deletions

View File

@ -27,6 +27,7 @@ import {
} from './plugins/oakComponent';
import { preLoadLocales } from './utils/locales';
import { createCommonPlugin } from './plugins/common';
import { initTriggerProgram } from './utils/triggers';
// 初始化配置
// 查找工作区的根目录中的oak.config.json文件排除src和node_modules目录
@ -90,6 +91,13 @@ const afterPathSet = async () => {
updateEntityComponent(norPath);
},
},
{
name: '初始化trigger信息',
description: '初始化trigger信息',
function: async () => {
initTriggerProgram();
},
},
];
await vscode.window.withProgress(
@ -156,7 +164,7 @@ export async function activate(context: vscode.ExtensionContext) {
oakPathInline,
oakPathCompletion.oakPathCompletion,
oakPathCompletion.oakPathDocumentLinkProvider,
...oakPathHighlighter,
...oakPathHighlighter
);
createFileWatcher(context);
} catch (error) {

View File

@ -1,3 +1,4 @@
import * as ts from 'typescript';
import { EntityShape, StorageDesc } from 'oak-domain/lib/types';
export type CreateComponentConfig = {
@ -102,10 +103,17 @@ export type RenderProps = {
};
export type TriggerDef = {
name: string;
entity: string;
action: string;
action: string[];
when: string;
path: string;
asRoot?: boolean;
priority?: number;
tsInfo: {
sourceFile: ts.SourceFile;
program: ts.Program;
typeChecker: ts.TypeChecker;
}
};
export type EntityLocale = {

View File

@ -0,0 +1,336 @@
import * as ts from 'typescript';
import { pathConfig } from './paths';
import { dirname, join } from 'path';
import { createProjectProgram } from './ts-utils';
import { TriggerDef } from '../types';
let triggerProgram: ts.Program | null = null;
export const initTriggerProgram = () => {
const open = createProjectProgram(join(pathConfig.triggerHome, 'index.ts'));
if (!open) {
console.error('trigger program init failed');
return;
}
triggerProgram = open;
const res = getDefaultExport();
console.log('trigger program inited:', res);
};
function resolveImportedTriggers(
node: ts.Node,
typeChecker: ts.TypeChecker,
sourceFile: ts.SourceFile,
program: ts.Program,
visited: Set<string> = new Set()
): TriggerDef[] {
if (ts.isSpreadElement(node)) {
return resolveImportedTriggers(
node.expression,
typeChecker,
sourceFile,
program,
visited
);
}
if (ts.isIdentifier(node)) {
const symbol = typeChecker.getSymbolAtLocation(node);
if (symbol && symbol.declarations && symbol.declarations.length > 0) {
const declaration = symbol.declarations[0];
if (
ts.isImportSpecifier(declaration) ||
ts.isImportClause(declaration)
) {
const importClause = declaration.parent
.parent as ts.ImportClause;
//ImportDeclaration
// ImportClause
// NamedImports
// ImportSpecifier
// Identifier
// Identifier
// StringLiteral
const importPath = importClause.parent.moduleSpecifier
.getText(sourceFile)
.slice(1, -1);
const importMeta = {
// 在目标文件中的名称
identifier: '',
// 导入到当前文件的名称
importName: '',
isDefault: false,
};
ts.forEachChild(importClause, (child) => {
if (ts.isNamedImports(child)) {
child.elements.forEach((element) => {
if (element.name.getText() === node.getText()) {
importMeta.identifier = element.name.getText();
importMeta.importName = element.propertyName
? element.propertyName.getText()
: element.name.getText();
}
});
} else if (ts.isNamespaceImport(child)) {
importMeta.identifier = child.name.getText();
importMeta.importName = child.name.getText();
} else if (ts.isImportSpecifier(child)) {
if (child.name.getText() === node.getText()) {
importMeta.identifier = child.name.getText();
importMeta.importName = child.propertyName
? child.propertyName.getText()
: child.name.getText();
}
}
});
const resolvedPath = join(
dirname(sourceFile.fileName),
importPath
);
if (visited.has(resolvedPath)) {
return []; // 防止循环引用
}
visited.add(resolvedPath);
const importedSourceFile = program.getSourceFile(
resolvedPath.endsWith('.ts')
? resolvedPath
: resolvedPath + '.ts'
);
if (importedSourceFile) {
return getTriggersFromSourceFile(
importedSourceFile,
typeChecker,
program,
visited,
importMeta
);
}
} else if (
ts.isVariableDeclaration(declaration) &&
declaration.initializer
) {
return resolveImportedTriggers(
declaration.initializer,
typeChecker,
sourceFile,
program,
visited
);
}
}
}
if (ts.isAsExpression(node)) {
return resolveImportedTriggers(
node.expression,
typeChecker,
sourceFile,
program,
visited
);
}
if (ts.isArrayLiteralExpression(node)) {
return node.elements.flatMap((element) =>
resolveImportedTriggers(
element,
typeChecker,
sourceFile,
program,
visited
)
);
}
if (ts.isObjectLiteralExpression(node)) {
const def: TriggerDef = {
name: '',
entity: '',
action: [],
when: '',
tsInfo: {
sourceFile,
program,
typeChecker,
},
};
ts.forEachChild(node, (child) => {
if (ts.isPropertyAssignment(child)) {
const key = child.name.getText();
if (ts.isStringLiteral(child.initializer)) {
const value = child.initializer;
if (key === 'name') {
def.name = value.text;
} else if (key === 'entity') {
def.entity = value.text;
} else if (key === 'action') {
def.action = [value.text];
} else if (key === 'when') {
def.when = value.text;
} else if (key === 'asRoot') {
def.asRoot = !!child.initializer;
} else if (key === 'priority') {
def.priority = parseInt(value.text);
}
} else if (ts.isArrayLiteralExpression(child.initializer)) {
if (key === 'action') {
def.action = child.initializer.elements.map(
(element) => {
if (ts.isStringLiteral(element)) {
return element.text;
}
return '';
}
);
}
}
}
});
return [def];
}
return [];
}
function getTriggersFromSourceFile(
sourceFile: ts.SourceFile,
typeChecker: ts.TypeChecker,
program: ts.Program,
visited: Set<string> = new Set(),
meta: {
identifier: string;
importName: string;
isDefault: boolean;
}
): TriggerDef[] {
let triggers: TriggerDef[] = [];
ts.forEachChild(sourceFile, (node) => {
if (!meta.isDefault) {
if (ts.isVariableStatement(node)) {
const declaration = node.declarationList.declarations[0];
if (
ts.isVariableDeclaration(declaration) &&
declaration.name.getText() === meta.importName &&
declaration.initializer
) {
triggers = resolveImportedTriggers(
declaration.initializer,
typeChecker,
sourceFile,
program,
visited
);
}
}
} else {
// 找export default的变量继续递归
ts.forEachChild(sourceFile, (node) => {
if (ts.isExportAssignment(node) && !node.isExportEquals) {
// 找到 export default 语句
if (ts.isIdentifier(node.expression)) {
// 如果 default export 是一个标识符
const symbol = typeChecker.getSymbolAtLocation(
node.expression
);
if (
symbol &&
symbol.declarations &&
symbol.declarations.length > 0
) {
const declaration = symbol.declarations[0];
if (
ts.isVariableDeclaration(declaration) &&
declaration.initializer
) {
triggers = resolveImportedTriggers(
declaration.initializer,
typeChecker,
sourceFile,
program,
visited
);
}
}
} else {
// 如果 default export 直接是一个表达式
triggers = resolveImportedTriggers(
node.expression,
typeChecker,
sourceFile,
program,
visited
);
}
}
});
}
});
return triggers;
}
export const getDefaultExport = (): TriggerDef[] => {
if (!triggerProgram) {
console.error('Trigger program not initialized');
return [];
}
const sourceFile = triggerProgram.getSourceFile(
join(pathConfig.triggerHome, 'index.ts')
);
if (!sourceFile) {
console.error('Source file not found');
return [];
}
const typeChecker = triggerProgram.getTypeChecker();
let defaultExportIdentifier: ts.Identifier | undefined;
// 查找默认导出
ts.forEachChild(sourceFile, (node) => {
if (ts.isExportAssignment(node) && !node.isExportEquals) {
if (ts.isIdentifier(node.expression)) {
defaultExportIdentifier = node.expression;
}
}
});
if (!defaultExportIdentifier) {
console.error('Default export not found');
return [];
}
const symbol = typeChecker.getSymbolAtLocation(defaultExportIdentifier);
if (!symbol) {
console.error('Symbol for default export not found');
return [];
}
const declaration =
symbol.valueDeclaration ||
(symbol.declarations && symbol.declarations[0]);
if (!declaration) {
console.error('Declaration for default export not found');
return [];
}
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
return resolveImportedTriggers(
declaration.initializer,
typeChecker,
sourceFile,
triggerProgram
);
}
console.error('Unable to resolve default export');
return [];
};

View File

@ -1,7 +1,8 @@
import ts from 'typescript';
import { DocumentValue, RenderProps } from '../types';
import * as vscode from 'vscode';
import { join } from 'path';
import path, { join } from 'path';
import { pathConfig } from './paths';
/**
* attrs
@ -427,7 +428,10 @@ export async function addAttrToFormData(
start: insertPos,
end: insertPos + insertText.length,
};
await vscode.commands.executeCommand('oak-assistant.jumpToPosition', args);
await vscode.commands.executeCommand(
'oak-assistant.jumpToPosition',
args
);
} else {
vscode.window.showErrorMessage('无法在 index.ts 中找到合适的插入位置');
}
@ -558,3 +562,47 @@ export async function addMethodToMethods(
};
await vscode.commands.executeCommand('oak-assistant.jumpToPosition', args);
}
/**
*
* @param filePath
*/
export const createProjectProgram = (filePath: string) => {
const projectRoot = pathConfig.projectHome;
// 查找配置文件
const configFileName = ts.findConfigFile(
projectRoot,
ts.sys.fileExists,
'tsconfig.json'
);
if (!configFileName) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
// 读取配置文件
const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
// 解析配置文件内容
const { options, fileNames, projectReferences } =
ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configFileName)
);
// 确保文件路径是绝对路径
const absoluteFilePath = path.isAbsolute(filePath)
? filePath
: path.join(projectRoot, filePath);
// 创建程序
const program = ts.createProgram({
rootNames: [absoluteFilePath],
options,
projectReferences,
});
return program;
};