完成trigger的扫描
This commit is contained in:
parent
f8440493d7
commit
571ab5b8d9
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue