entity tree panel 跳转到定义,lint等

This commit is contained in:
pqcqaq 2024-10-19 21:50:13 +08:00
parent 2e98c4e96d
commit 6e331b1221
13 changed files with 2525 additions and 1811 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
single_quoted_string = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
node_modules/
packages/react-live/dist/
website/build/
src/app/routers/

8
.eslintrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"parserOptions": {
"ecmaVersion": "latest"
},
"env": {
"es6": true
}
}

10
.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"useTabs": false,
"tabWidth": 4,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "es5",
"semi": true,
"endOfLine": "lf",
"jsxSingleQuote": true
}

1
assets/oak-icon.svg Normal file
View File

@ -0,0 +1 @@
<svg t="1729342159733" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1478" width="1280" height="1280"><path d="M512 64c-33.066667 0-63.786667 10.517333-85.290667 32.042667-10.304 10.304-17.728 22.912-23.04 36.672-9.621333-2.901333-19.328-5.909333-28.970666-5.717334-25.941333 0.512-49.749333 11.349333-68.117334 27.882667-25.450667 22.912-40.085333 58.624-38.250666 96.576-36.202667 2.474667-68.992 19.776-88.106667 47.829333-20.288 29.738667-23.466667 70.613333-6.890667 108.330667a118.058667 118.058667 0 0 0-37.418666 32.085333A144.128 144.128 0 0 0 106.666667 527.04c0 40.128 17.258667 80.277333 50.56 107.2 26.197333 21.226667 63.68 29.013333 104.896 27.008 6.165333 31.914667 18.304 61.653333 41.258666 83.328C333.994667 773.504 377.322667 789.333333 426.666667 789.333333v32c0 15.616-5.226667 43.562667-17.258667 63.36-12.010667 19.84-26.368 32.64-57.408 32.64a32 32 0 0 0 0 64h320a32 32 0 0 0 0-64c-31.04 0-45.397333-12.8-57.408-32.64-12.032-19.797333-17.258667-47.744-17.258667-63.36V789.333333c49.344 0 92.672-15.829333 123.306667-44.757333 22.933333-21.674667 35.072-51.413333 41.237333-83.328 41.216 1.984 78.72-5.781333 104.917334-27.008C900.053333 607.317333 917.333333 567.168 917.333333 527.04c0-30.848-9.813333-61.909333-29.248-87.338667a118.058667 118.058667 0 0 0-37.418666-32.085333c16.576-37.717333 13.397333-78.592-6.869334-108.330667-19.136-28.053333-51.925333-45.354667-88.128-47.829333 1.834667-37.952-12.8-73.664-38.250666-96.576-18.368-16.533333-42.176-27.370667-68.117334-27.882667-9.642667-0.192-19.349333 2.816-28.970666 5.717334-5.312-13.76-12.736-26.368-23.04-36.672C575.765333 74.517333 545.066667 64 512 64z m0 64c20.266667 0 32.213333 5.482667 40.042667 13.290667 7.808 7.808 13.290667 19.776 13.290666 40.042666a32 32 0 0 0 54.613334 22.613334c10.666667-10.645333 19.392-13.12 28.096-12.949334 8.704 0.170667 18.24 3.989333 26.538666 11.456 16.597333 14.933333 26.602667 40.981333 11.456 71.253334a32 32 0 0 0 39.893334 44.245333c29.930667-11.221333 53.077333 0.021333 64.938666 17.429333 11.861333 17.386667 15.210667 39.957333-13.504 68.650667A32 32 0 0 0 800 458.666667c16.042667 0 27.349333 6.954667 37.248 19.925333 9.898667 12.949333 16.085333 32.064 16.085333 48.448 0 21.76-9.408 43.434667-26.794666 57.493333-17.365333 14.08-43.178667 22.528-83.584 13.546667A32 32 0 0 0 704 629.333333c0 29.717333-9.92 52.352-27.306667 68.757334C659.349333 714.496 633.365333 725.333333 597.333333 725.333333v-32a32 32 0 1 0-64 0v128c0 26.88 6.229333 62.656 26.282667 96h-95.232c20.053333-33.344 26.282667-69.12 26.282667-96v-128a32 32 0 0 0-32.490667-32.448A32 32 0 0 0 426.666667 693.333333V725.333333c-36.010667 0-61.994667-10.837333-79.36-27.242666-17.386667-16.426667-27.306667-39.04-27.306667-68.757334a32 32 0 0 0-38.954667-31.253333c-40.405333 8.96-66.218667 0.533333-83.584-13.546667C180.053333 570.474667 170.666667 548.8 170.666667 527.04c0-16.384 6.186667-35.498667 16.085333-48.448 9.898667-12.970667 21.205333-19.925333 37.248-19.925333a32 32 0 0 0 22.613333-54.613334c-28.693333-28.714667-25.344-51.285333-13.482666-68.693333 11.861333-17.386667 35.008-28.629333 64.96-17.408a32 32 0 0 0 39.872-44.245333c-15.146667-30.272-5.12-56.32 11.456-71.253334 8.298667-7.466667 17.834667-11.285333 26.538666-11.456 8.704-0.170667 17.429333 2.304 28.074667 12.970667A32 32 0 0 0 458.666667 181.333333c0-20.266667 5.482667-32.213333 13.290666-40.042666C479.765333 133.482667 491.733333 128 512 128z" p-id="1479"></path></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -2,6 +2,11 @@
"name": "oak-assistant", "name": "oak-assistant",
"displayName": "oak-assistant", "displayName": "oak-assistant",
"description": "OAK框架辅助开发插件", "description": "OAK框架辅助开发插件",
"author": {
"name": "oak-team"
},
"icon": "assets/oak-icon.svg",
"publisher": "oak-team",
"version": "0.0.1", "version": "0.0.1",
"engines": { "engines": {
"vscode": "^1.94.0" "vscode": "^1.94.0"
@ -26,6 +31,12 @@
{ {
"command": "oak-assistant.create-oak-component", "command": "oak-assistant.create-oak-component",
"title": "创建OAK组件" "title": "创建OAK组件"
},
{
"title": "跳转到entity定义",
"command": "oak-entities.jumpToDefinition",
"when": "view == oak-entities && viewItem == entityItem",
"group": "navigation"
} }
], ],
"menus": { "menus": {
@ -35,6 +46,30 @@
"command": "oak-assistant.create-oak-component", "command": "oak-assistant.create-oak-component",
"group": "navigation" "group": "navigation"
} }
],
"view/item/context": [
{
"command": "oak-entities.jumpToDefinition",
"when": "view == oak-entities && viewItem == entityItem",
"group": "navigation"
}
]
},
"viewsContainers": {
"activitybar": [
{
"id": "oak-entities",
"title": "Oak Entities",
"icon": "assets/oak-icon.svg"
}
]
},
"views": {
"oak-entities": [
{
"id": "oak-entities",
"name": "Oak Entities"
}
] ]
} }
}, },
@ -69,6 +104,7 @@
"typescript": "^5.6.2" "typescript": "^5.6.2"
}, },
"dependencies": { "dependencies": {
"glob": "^11.0.0",
"lodash": "^4.17.21" "lodash": "^4.17.21"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,9 @@ import checkPagesAndNamespace from "./plugins/checkPagesAndNamespace";
import { OakConfiog } from "./types/OakConfig"; import { OakConfiog } from "./types/OakConfig";
import createOakComponent from "./plugins/createOakComponent"; import createOakComponent from "./plugins/createOakComponent";
import { analyzeOakAppDomain } from "./utils/entities"; import { analyzeOakAppDomain } from "./utils/entities";
import { createOakTreePanel } from "./plugins/oakTreePanel";
import { setLoadingEntities } from "./utils/status";
import { treePanelCommands } from "./plugins/treePanelCommands";
// 初始化配置 // 初始化配置
// 查找工作区的根目录中的oak.config.json文件排除src和node_modules目录 // 查找工作区的根目录中的oak.config.json文件排除src和node_modules目录
@ -71,6 +74,8 @@ vscode.workspace.findFiles("oak.config.json", exclude).then((uris) => {
}); });
const afterPathSet = async () => { const afterPathSet = async () => {
setLoadingEntities(true);
const stepList: { const stepList: {
name: string; name: string;
description: string; description: string;
@ -108,6 +113,8 @@ const afterPathSet = async () => {
} }
} }
); );
setLoadingEntities(false);
}; };
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
@ -127,7 +134,9 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push( context.subscriptions.push(
helloOak, helloOak,
checkPagesAndNamespace(), checkPagesAndNamespace(),
createOakComponent() createOakComponent(),
createOakTreePanel(),
...treePanelCommands
); );
} }

View File

@ -0,0 +1,82 @@
import * as vscode from 'vscode';
import { entityConfig, subscribe } from '../utils/entities';
class OakTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {
private listenerDispose: (() => void) | null = null;
private _onDidChangeTreeData: vscode.EventEmitter<
TreeItem | undefined | null | void
> = new vscode.EventEmitter<TreeItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<
TreeItem | undefined | null | void
> = this._onDidChangeTreeData.event;
public refresh(): void {
this._onDidChangeTreeData.fire();
}
// 初始化方法
constructor() {
this.listenerDispose = subscribe(() => {
this.refresh();
});
}
getTreeItem(
element: TreeItem
): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element;
}
getChildren(element?: TreeItem): vscode.ProviderResult<TreeItem[]> {
// 最顶层
if (!element) {
return entityConfig.entityNameList.map((entityName) => {
return new EntityItem(
entityName,
vscode.TreeItemCollapsibleState.Collapsed
);
});
}
}
// 销毁方法
dispose() {
if (this.listenerDispose) {
this.listenerDispose();
}
}
}
class TreeItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(label, collapsibleState);
}
}
export class EntityItem extends TreeItem {
private readonly entityName: string;
getEntityName() {
return this.entityName;
}
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.entityName = label;
this.contextValue = 'entityItem'; // 添加这行,用于识别右键菜单项
}
}
export const createOakTreePanel = () => {
const treeDataProvider = new OakTreeDataProvider();
const treeView = vscode.window.createTreeView('oak-entities', {
treeDataProvider: treeDataProvider,
});
return treeView;
};

View File

@ -0,0 +1,66 @@
import * as vscode from 'vscode';
import { pathConfig } from '../utils/paths';
import { join, resolve } from 'path';
import { toUpperFirst } from '../utils/stringUtils';
import * as fs from 'fs';
import * as glob from 'glob';
import { EntityItem } from './oakTreePanel';
const pushToEntityDefinition = vscode.commands.registerCommand(
'oak-entities.jumpToDefinition',
async (item: EntityItem) => {
if (!item) {
// 在explorer中定位到指定文件夹
const dir = pathConfig.entityHome;
const uri = vscode.Uri.file(dir);
await vscode.commands.executeCommand('revealInExplorer', uri);
return;
}
const fileName = toUpperFirst(`${item.getEntityName()}.ts`);
const possiblePaths: string[] = [];
// 搜索 pathConfig.entityHome
const entityHomePath = join(pathConfig.entityHome, fileName);
if (fs.existsSync(entityHomePath)) {
possiblePaths.push(entityHomePath);
}
// 搜索 node_modules 中以 oak 开头的包
const nodeModulesPath = resolve(pathConfig.projectHome, 'node_modules');
const oakPackages = glob.sync('oak-*/src/entities', {
cwd: nodeModulesPath,
});
for (const pkg of oakPackages) {
const pkgEntityPath = join(nodeModulesPath, pkg, fileName);
if (fs.existsSync(pkgEntityPath)) {
possiblePaths.push(pkgEntityPath);
}
}
if (possiblePaths.length === 0) {
vscode.window.showErrorMessage(
`没有找到entity的定义文件: ${item.getEntityName()}`
);
return;
}
let selectedPath: string;
if (possiblePaths.length === 1) {
selectedPath = possiblePaths[0];
} else {
const selected = await vscode.window.showQuickPick(
possiblePaths.map((path) => ({ label: path, description: '' })),
{ placeHolder: '选择一个entity定义文件' }
);
if (!selected) {
return;
}
selectedPath = selected.label;
}
const entityDefinitionUri = vscode.Uri.file(selectedPath);
await vscode.window.showTextDocument(entityDefinitionUri);
}
);
export const treePanelCommands = [pushToEntityDefinition];

View File

@ -1,337 +1,380 @@
import * as vscode from "vscode"; import * as vscode from 'vscode';
import { EntityShape } from "oak-domain/lib/types"; import { EntityShape } from 'oak-domain/lib/types';
import { StorageDesc } from "oak-domain/lib/types/Storage"; import { StorageDesc } from 'oak-domain/lib/types/Storage';
import { join, dirname } from "path"; import { join, dirname } from 'path';
import fs from "fs"; import fs from 'fs';
import * as ts from "typescript"; import * as ts from 'typescript';
import { debounce } from 'lodash';
import { random } from 'lodash';
export type EntityDict = { export type EntityDict = {
[key: string]: StorageDesc<EntityShape>; [key: string]: StorageDesc<EntityShape>;
}; };
const entityDict: EntityDict = {}; // 发布订阅模式
const subscribers: Map<number, () => void> = new Map();
const updateDeounced = debounce(() => {
subscribers.forEach((callback) => callback());
}, 100);
const entityDict: EntityDict = new Proxy({} as EntityDict, {
set(target, key, value) {
target[key as string] = value;
updateDeounced();
return true;
},
});
export const subscribe = (callback: () => void) => {
/**
*
*
* @param callback
* @returns key
*/
const setToSubscribers = (callback: () => void) => {
const key = random(0, 100000);
if (subscribers.has(key)) {
return setToSubscribers(callback);
}
subscribers.set(key, callback);
return key;
};
const key = setToSubscribers(callback);
return () => {
subscribers.delete(key);
};
};
const genEntityNameList = (): string[] => { const genEntityNameList = (): string[] => {
return Object.keys(entityDict); return Object.keys(entityDict);
}; };
export const entityConfig = { export const entityConfig = {
get entityNameList() { get entityNameList() {
return genEntityNameList(); return genEntityNameList();
}, },
}; };
function resolveImportPath(importPath: string, currentDir: string): string { function resolveImportPath(importPath: string, currentDir: string): string {
if (importPath.startsWith(".")) { if (importPath.startsWith('.')) {
return join(currentDir, `${importPath}.ts`); return join(currentDir, `${importPath}.ts`);
} }
// 处理非相对路径的导入(如 node_modules // 处理非相对路径的导入(如 node_modules
return importPath; return importPath;
} }
function getEvaluateNodeForShorthandProperty( function getEvaluateNodeForShorthandProperty(
program: ts.Program, program: ts.Program,
node: ts.ShorthandPropertyAssignment, node: ts.ShorthandPropertyAssignment,
typeChecker: ts.TypeChecker typeChecker: ts.TypeChecker
): any { ): any {
const symbol = typeChecker.getSymbolAtLocation(node.name); const symbol = typeChecker.getSymbolAtLocation(node.name);
if (!symbol) { if (!symbol) {
return undefined; return undefined;
} }
// 获取符号的声明 // 获取符号的声明
const declarations = symbol.declarations; const declarations = symbol.declarations;
if (!declarations || declarations.length === 0) { if (!declarations || declarations.length === 0) {
return undefined; return undefined;
} }
const declaration = declarations[0]; const declaration = declarations[0];
// 从当前文件的所有导入中找到对应的导入 // 从当前文件的所有导入中找到对应的导入
const sourceFile = declaration.getSourceFile(); const sourceFile = declaration.getSourceFile();
let propertyName = ""; let propertyName = '';
const importDeclaration = sourceFile.statements.find((statement) => { const importDeclaration = sourceFile.statements.find((statement) => {
// 在这里找到import { actions } from "./Actions" 这样的形式 // 在这里找到import { actions } from "./Actions" 这样的形式
if (ts.isImportDeclaration(statement)) { if (ts.isImportDeclaration(statement)) {
const moduleSpecifier = statement.moduleSpecifier; const moduleSpecifier = statement.moduleSpecifier;
if (ts.isStringLiteral(moduleSpecifier)) { if (ts.isStringLiteral(moduleSpecifier)) {
const imports = statement.importClause?.namedBindings; const imports = statement.importClause?.namedBindings;
// 如果导入了node.name // 如果导入了node.name
if (imports && ts.isNamedImports(imports)) { if (imports && ts.isNamedImports(imports)) {
// 这里需要注意如果是import { generalActions as actions } from "./Actions" 这样的形式要拿到as的内容和node.name进行比较 // 这里需要注意如果是import { generalActions as actions } from "./Actions" 这样的形式要拿到as的内容和node.name进行比较
return imports.elements.some((element) => { return imports.elements.some((element) => {
if (ts.isImportSpecifier(element)) { if (ts.isImportSpecifier(element)) {
if (element.propertyName) { if (element.propertyName) {
propertyName = element.propertyName.getText(); propertyName = element.propertyName.getText();
// 这里要确保是as actions里的actions和node.name进行比较 // 这里要确保是as actions里的actions和node.name进行比较
return element return element
.getText() .getText()
.endsWith(` as ${node.getText()}`); .endsWith(` as ${node.getText()}`);
} }
// 这里是import { actions } from "./Actions" 这样的形式 // 这里是import { actions } from "./Actions" 这样的形式
propertyName = element.name.getText(); propertyName = element.name.getText();
return element.name.getText() === node.getText(); return element.name.getText() === node.getText();
} }
return false; return false;
}); });
} }
} }
} }
return false; return false;
}) as ts.ImportDeclaration | undefined; }) as ts.ImportDeclaration | undefined;
// 这里对包内的genericActions做特殊处理 // 这里对包内的genericActions做特殊处理
if (propertyName === "genericActions") { if (propertyName === 'genericActions') {
return [ return [
"count", 'count',
"stat", 'stat',
"download", 'download',
"select", 'select',
"aggregate", 'aggregate',
"create", 'create',
"remove", 'remove',
"update", 'update',
]; ];
} }
if (importDeclaration) { if (importDeclaration) {
// 得到导入的路径 // 得到导入的路径
const importPath = ( const importPath = (
importDeclaration.moduleSpecifier as ts.StringLiteral importDeclaration.moduleSpecifier as ts.StringLiteral
).text; ).text;
const currentSourceFile = node.getSourceFile(); const currentSourceFile = node.getSourceFile();
const resolvedPath = resolveImportPath( const resolvedPath = resolveImportPath(
importPath, importPath,
dirname(currentSourceFile.fileName) dirname(currentSourceFile.fileName)
); );
// 创建新的程序来解析导入的文件 // 创建新的程序来解析导入的文件
const importProgram = ts.createProgram( const importProgram = ts.createProgram(
[resolvedPath], [resolvedPath],
program.getCompilerOptions() program.getCompilerOptions()
); );
const importSourceFile = importProgram.getSourceFile(resolvedPath); const importSourceFile = importProgram.getSourceFile(resolvedPath);
if (importSourceFile) { if (importSourceFile) {
let foundDeclaration: ts.Node | undefined; let foundDeclaration: ts.Node | undefined;
ts.forEachChild(importSourceFile, (child) => { ts.forEachChild(importSourceFile, (child) => {
if (ts.isVariableStatement(child)) { if (ts.isVariableStatement(child)) {
const declaration = child.declarationList.declarations[0]; const declaration = child.declarationList.declarations[0];
if ( if (
ts.isIdentifier(declaration.name) && ts.isIdentifier(declaration.name) &&
declaration.name.text === propertyName declaration.name.text === propertyName
) { ) {
foundDeclaration = declaration; foundDeclaration = declaration;
} }
} else if ( } else if (
ts.isFunctionDeclaration(child) && ts.isFunctionDeclaration(child) &&
child.name && child.name &&
child.name.text === propertyName child.name.text === propertyName
) { ) {
foundDeclaration = child; foundDeclaration = child;
} else if ( } else if (
ts.isExportAssignment(child) && ts.isExportAssignment(child) &&
ts.isIdentifier(child.expression) && ts.isIdentifier(child.expression) &&
child.expression.text === propertyName child.expression.text === propertyName
) { ) {
foundDeclaration = child; foundDeclaration = child;
} }
}); });
if (foundDeclaration) { if (foundDeclaration) {
return evaluateNode( return evaluateNode(
importProgram, importProgram,
foundDeclaration, foundDeclaration,
importProgram.getTypeChecker() importProgram.getTypeChecker()
); );
} }
} }
} }
// 如果没有找到导入声明,则假设它是当前文件中的定义 // 如果没有找到导入声明,则假设它是当前文件中的定义
return evaluateNode(program, declaration, typeChecker); return evaluateNode(program, declaration, typeChecker);
} }
function evaluateNode( function evaluateNode(
program: ts.Program, program: ts.Program,
node: ts.Node, node: ts.Node,
typeChecker: ts.TypeChecker typeChecker: ts.TypeChecker
): any { ): any {
if (ts.isObjectLiteralExpression(node)) { if (ts.isObjectLiteralExpression(node)) {
return node.properties.reduce((obj: any, prop) => { return node.properties.reduce((obj: any, prop) => {
if (ts.isShorthandPropertyAssignment(prop)) { if (ts.isShorthandPropertyAssignment(prop)) {
// 得到标识符的名称 // 得到标识符的名称
const name = prop.name.getText(); const name = prop.name.getText();
const evaluated = getEvaluateNodeForShorthandProperty( const evaluated = getEvaluateNodeForShorthandProperty(
program, program,
prop, prop,
typeChecker typeChecker
); );
obj[name] = evaluated; obj[name] = evaluated;
} else if (ts.isPropertyAssignment(prop)) { } else if (ts.isPropertyAssignment(prop)) {
const name = prop.name.getText(); const name = prop.name.getText();
obj[name] = evaluateNode( obj[name] = evaluateNode(
program, program,
prop.initializer, prop.initializer,
typeChecker typeChecker
); );
} }
return obj; return obj;
}, {}); }, {});
} else if (ts.isArrayLiteralExpression(node)) { } else if (ts.isArrayLiteralExpression(node)) {
return node.elements.map((element) => return node.elements.map((element) =>
evaluateNode(program, element, typeChecker) evaluateNode(program, element, typeChecker)
); );
} else if (ts.isStringLiteral(node)) { } else if (ts.isStringLiteral(node)) {
return node.text; return node.text;
} else if (ts.isNumericLiteral(node)) { } else if (ts.isNumericLiteral(node)) {
return Number(node.text); return Number(node.text);
} else if (node.kind === ts.SyntaxKind.TrueKeyword) { } else if (node.kind === ts.SyntaxKind.TrueKeyword) {
return true; return true;
} else if (node.kind === ts.SyntaxKind.FalseKeyword) { } else if (node.kind === ts.SyntaxKind.FalseKeyword) {
return false; return false;
} else if (ts.isIdentifier(node)) { } else if (ts.isIdentifier(node)) {
// 处理导入的标识符 // 处理导入的标识符
const symbol = typeChecker.getSymbolAtLocation(node); const symbol = typeChecker.getSymbolAtLocation(node);
if (symbol && symbol.valueDeclaration) { if (symbol && symbol.valueDeclaration) {
return evaluateNode(program, symbol.valueDeclaration, typeChecker); return evaluateNode(program, symbol.valueDeclaration, typeChecker);
} }
} else if (ts.isVariableDeclaration(node) && node.initializer) { } else if (ts.isVariableDeclaration(node) && node.initializer) {
// 处理变量声明 // 处理变量声明
return evaluateNode(program, node.initializer, typeChecker); return evaluateNode(program, node.initializer, typeChecker);
} }
// 对于其他类型的节点,可能需要进一步处理 // 对于其他类型的节点,可能需要进一步处理
return undefined; return undefined;
} }
function parseDescFile( function parseDescFile(
filePath: string, filePath: string,
program: ts.Program program: ts.Program
): StorageDesc<EntityShape> | null { ): StorageDesc<EntityShape> | null {
const sourceFile = program.getSourceFile(filePath); const sourceFile = program.getSourceFile(filePath);
if (!sourceFile) { if (!sourceFile) {
vscode.window.showWarningMessage(`无法解析文件: ${filePath}`); vscode.window.showWarningMessage(`无法解析文件: ${filePath}`);
return null; return null;
} }
const typeChecker = program.getTypeChecker(); const typeChecker = program.getTypeChecker();
let descObject: StorageDesc<EntityShape> | null = null; let descObject: StorageDesc<EntityShape> | null = null;
ts.forEachChild(sourceFile, (node) => { ts.forEachChild(sourceFile, (node) => {
if (ts.isVariableStatement(node)) { if (ts.isVariableStatement(node)) {
const declaration = node.declarationList.declarations[0]; const declaration = node.declarationList.declarations[0];
if ( if (
ts.isIdentifier(declaration.name) && ts.isIdentifier(declaration.name) &&
declaration.name.text === "desc" declaration.name.text === 'desc'
) { ) {
if ( if (
declaration.initializer && declaration.initializer &&
ts.isObjectLiteralExpression(declaration.initializer) ts.isObjectLiteralExpression(declaration.initializer)
) { ) {
descObject = evaluateNode( descObject = evaluateNode(
program, program,
declaration.initializer, declaration.initializer,
typeChecker typeChecker
); );
} }
} }
} }
}); });
return descObject; return descObject;
} }
export const analyzeOakAppDomain = (path: string) => { export const analyzeOakAppDomain = (path: string) => {
const storageFile = join(path, "Storage.ts"); // 开始分析先清空entityDict
Object.keys(entityDict).forEach((key) => {
delete entityDict[key];
});
if (!fs.existsSync(storageFile)) { const storageFile = join(path, 'Storage.ts');
vscode.window.showErrorMessage(
"Storage.ts文件不存在请先尝试make:domain"
);
return;
}
const program = ts.createProgram([storageFile], {}); if (!fs.existsSync(storageFile)) {
const sourceFile = program.getSourceFile(storageFile); vscode.window.showErrorMessage(
'Storage.ts文件不存在请先尝试make:domain'
);
return;
}
if (!sourceFile) { const program = ts.createProgram([storageFile], {});
vscode.window.showErrorMessage("无法解析Storage.ts文件"); const sourceFile = program.getSourceFile(storageFile);
return;
}
let storageSchemaNode: ts.Node | undefined; if (!sourceFile) {
vscode.window.showErrorMessage('无法解析Storage.ts文件');
return;
}
ts.forEachChild(sourceFile, (node) => { let storageSchemaNode: ts.Node | undefined;
if (ts.isVariableStatement(node)) {
const declaration = node.declarationList.declarations[0];
if (
ts.isIdentifier(declaration.name) &&
declaration.name.text === "storageSchema"
) {
storageSchemaNode = declaration.initializer;
}
}
});
if ( ts.forEachChild(sourceFile, (node) => {
!storageSchemaNode || if (ts.isVariableStatement(node)) {
!ts.isObjectLiteralExpression(storageSchemaNode) const declaration = node.declarationList.declarations[0];
) { if (
vscode.window.showErrorMessage("无法找到storageSchema或格式不正确"); ts.isIdentifier(declaration.name) &&
return; declaration.name.text === 'storageSchema'
} ) {
storageSchemaNode = declaration.initializer;
}
}
});
const importMap: { [key: string]: string } = {}; if (
!storageSchemaNode ||
!ts.isObjectLiteralExpression(storageSchemaNode)
) {
vscode.window.showErrorMessage('无法找到storageSchema或格式不正确');
return;
}
ts.forEachChild(sourceFile, (node) => { const importMap: { [key: string]: string } = {};
if (ts.isImportDeclaration(node)) {
const moduleSpecifier = node.moduleSpecifier;
if (ts.isStringLiteral(moduleSpecifier)) {
const importPath = moduleSpecifier.text;
const importClause = node.importClause;
if (
importClause &&
importClause.namedBindings &&
ts.isNamedImports(importClause.namedBindings)
) {
importClause.namedBindings.elements.forEach((element) => {
if (
element.propertyName &&
element.propertyName.text === "desc"
) {
importMap[element.name.text] = importPath;
}
});
}
}
}
});
storageSchemaNode.properties.forEach((prop) => { ts.forEachChild(sourceFile, (node) => {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { if (ts.isImportDeclaration(node)) {
const entityName = prop.name.text; const moduleSpecifier = node.moduleSpecifier;
if (ts.isIdentifier(prop.initializer)) { if (ts.isStringLiteral(moduleSpecifier)) {
const descName = prop.initializer.text; const importPath = moduleSpecifier.text;
const importPath = importMap[descName]; const importClause = node.importClause;
if (importPath) { if (
const resolvedPath = resolveImportPath( importClause &&
importPath, importClause.namedBindings &&
dirname(storageFile) ts.isNamedImports(importClause.namedBindings)
); ) {
const descObject = parseDescFile(resolvedPath, program); importClause.namedBindings.elements.forEach((element) => {
if (descObject) { if (
entityDict[entityName] = descObject; element.propertyName &&
} element.propertyName.text === 'desc'
} else { ) {
vscode.window.showWarningMessage( importMap[element.name.text] = importPath;
`未找到 ${descName} 的导入路径` }
); });
} }
} else { }
vscode.window.showWarningMessage( }
`${entityName} 的值不是预期的标识符` });
);
}
}
});
console.log("entityDict:", entityDict); storageSchemaNode.properties.forEach((prop) => {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
const entityName = prop.name.text;
if (ts.isIdentifier(prop.initializer)) {
const descName = prop.initializer.text;
const importPath = importMap[descName];
if (importPath) {
const resolvedPath = resolveImportPath(
importPath,
dirname(storageFile)
);
const descObject = parseDescFile(resolvedPath, program);
if (descObject) {
entityDict[entityName] = descObject;
}
} else {
vscode.window.showWarningMessage(
`未找到 ${descName} 的导入路径`
);
}
} else {
vscode.window.showWarningMessage(
`${entityName} 的值不是预期的标识符`
);
}
}
});
console.log('entityDict:', entityDict);
}; };

24
src/utils/status.ts Normal file
View File

@ -0,0 +1,24 @@
const globalStatus = {
isLoadingEntities: false,
};
export const setLoadingEntities = (loading: boolean) => {
globalStatus.isLoadingEntities = loading;
};
export const isLoadingEntities = () => {
return globalStatus.isLoadingEntities;
};
export const waitUntilEntitiesLoaded = async () => {
return new Promise<boolean>((resolve) => {
const check = () => {
if (globalStatus.isLoadingEntities) {
setTimeout(check, 100);
} else {
resolve(true);
}
};
check();
});
};

4
src/utils/stringUtils.ts Normal file
View File

@ -0,0 +1,4 @@
// 将驼峰命名的entityName转换为大写开头
export const toUpperFirst = (entityName: string) => {
return entityName.charAt(0).toUpperCase() + entityName.slice(1);
};