entity tree panel 跳转到定义,lint等
This commit is contained in:
parent
2e98c4e96d
commit
6e331b1221
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
node_modules/
|
||||
packages/react-live/dist/
|
||||
website/build/
|
||||
src/app/routers/
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"env": {
|
||||
"es6": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"printWidth": 80,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"semi": true,
|
||||
"endOfLine": "lf",
|
||||
"jsxSingleQuote": true
|
||||
}
|
||||
|
|
@ -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 |
36
package.json
36
package.json
|
|
@ -2,6 +2,11 @@
|
|||
"name": "oak-assistant",
|
||||
"displayName": "oak-assistant",
|
||||
"description": "OAK框架辅助开发插件",
|
||||
"author": {
|
||||
"name": "oak-team"
|
||||
},
|
||||
"icon": "assets/oak-icon.svg",
|
||||
"publisher": "oak-team",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.94.0"
|
||||
|
|
@ -26,6 +31,12 @@
|
|||
{
|
||||
"command": "oak-assistant.create-oak-component",
|
||||
"title": "创建OAK组件"
|
||||
},
|
||||
{
|
||||
"title": "跳转到entity定义",
|
||||
"command": "oak-entities.jumpToDefinition",
|
||||
"when": "view == oak-entities && viewItem == entityItem",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
|
@ -35,6 +46,30 @@
|
|||
"command": "oak-assistant.create-oak-component",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": "^11.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
3454
pnpm-lock.yaml
3454
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -5,6 +5,9 @@ import checkPagesAndNamespace from "./plugins/checkPagesAndNamespace";
|
|||
import { OakConfiog } from "./types/OakConfig";
|
||||
import createOakComponent from "./plugins/createOakComponent";
|
||||
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目录
|
||||
|
|
@ -71,6 +74,8 @@ vscode.workspace.findFiles("oak.config.json", exclude).then((uris) => {
|
|||
});
|
||||
|
||||
const afterPathSet = async () => {
|
||||
setLoadingEntities(true);
|
||||
|
||||
const stepList: {
|
||||
name: string;
|
||||
description: string;
|
||||
|
|
@ -108,6 +113,8 @@ const afterPathSet = async () => {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
setLoadingEntities(false);
|
||||
};
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
|
|
@ -127,7 +134,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(
|
||||
helloOak,
|
||||
checkPagesAndNamespace(),
|
||||
createOakComponent()
|
||||
createOakComponent(),
|
||||
createOakTreePanel(),
|
||||
...treePanelCommands
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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];
|
||||
|
|
@ -1,337 +1,380 @@
|
|||
import * as vscode from "vscode";
|
||||
import { EntityShape } from "oak-domain/lib/types";
|
||||
import { StorageDesc } from "oak-domain/lib/types/Storage";
|
||||
import { join, dirname } from "path";
|
||||
import fs from "fs";
|
||||
import * as ts from "typescript";
|
||||
import * as vscode from 'vscode';
|
||||
import { EntityShape } from 'oak-domain/lib/types';
|
||||
import { StorageDesc } from 'oak-domain/lib/types/Storage';
|
||||
import { join, dirname } from 'path';
|
||||
import fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
import { debounce } from 'lodash';
|
||||
import { random } from 'lodash';
|
||||
|
||||
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[] => {
|
||||
return Object.keys(entityDict);
|
||||
return Object.keys(entityDict);
|
||||
};
|
||||
|
||||
export const entityConfig = {
|
||||
get entityNameList() {
|
||||
return genEntityNameList();
|
||||
},
|
||||
get entityNameList() {
|
||||
return genEntityNameList();
|
||||
},
|
||||
};
|
||||
|
||||
function resolveImportPath(importPath: string, currentDir: string): string {
|
||||
if (importPath.startsWith(".")) {
|
||||
return join(currentDir, `${importPath}.ts`);
|
||||
}
|
||||
// 处理非相对路径的导入(如 node_modules)
|
||||
return importPath;
|
||||
if (importPath.startsWith('.')) {
|
||||
return join(currentDir, `${importPath}.ts`);
|
||||
}
|
||||
// 处理非相对路径的导入(如 node_modules)
|
||||
return importPath;
|
||||
}
|
||||
|
||||
function getEvaluateNodeForShorthandProperty(
|
||||
program: ts.Program,
|
||||
node: ts.ShorthandPropertyAssignment,
|
||||
typeChecker: ts.TypeChecker
|
||||
program: ts.Program,
|
||||
node: ts.ShorthandPropertyAssignment,
|
||||
typeChecker: ts.TypeChecker
|
||||
): any {
|
||||
const symbol = typeChecker.getSymbolAtLocation(node.name);
|
||||
if (!symbol) {
|
||||
return undefined;
|
||||
}
|
||||
const symbol = typeChecker.getSymbolAtLocation(node.name);
|
||||
if (!symbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 获取符号的声明
|
||||
const declarations = symbol.declarations;
|
||||
if (!declarations || declarations.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// 获取符号的声明
|
||||
const declarations = symbol.declarations;
|
||||
if (!declarations || declarations.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const declaration = declarations[0];
|
||||
// 从当前文件的所有导入中找到对应的导入
|
||||
const sourceFile = declaration.getSourceFile();
|
||||
let propertyName = "";
|
||||
const importDeclaration = sourceFile.statements.find((statement) => {
|
||||
// 在这里找到import { actions } from "./Actions" 这样的形式
|
||||
if (ts.isImportDeclaration(statement)) {
|
||||
const moduleSpecifier = statement.moduleSpecifier;
|
||||
if (ts.isStringLiteral(moduleSpecifier)) {
|
||||
const imports = statement.importClause?.namedBindings;
|
||||
// 如果导入了node.name
|
||||
if (imports && ts.isNamedImports(imports)) {
|
||||
// 这里需要注意,如果是import { generalActions as actions } from "./Actions" 这样的形式,要拿到as的内容和node.name进行比较
|
||||
return imports.elements.some((element) => {
|
||||
if (ts.isImportSpecifier(element)) {
|
||||
if (element.propertyName) {
|
||||
propertyName = element.propertyName.getText();
|
||||
// 这里要确保是as actions里的actions和node.name进行比较
|
||||
return element
|
||||
.getText()
|
||||
.endsWith(` as ${node.getText()}`);
|
||||
}
|
||||
// 这里是import { actions } from "./Actions" 这样的形式
|
||||
propertyName = element.name.getText();
|
||||
return element.name.getText() === node.getText();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) as ts.ImportDeclaration | undefined;
|
||||
const declaration = declarations[0];
|
||||
// 从当前文件的所有导入中找到对应的导入
|
||||
const sourceFile = declaration.getSourceFile();
|
||||
let propertyName = '';
|
||||
const importDeclaration = sourceFile.statements.find((statement) => {
|
||||
// 在这里找到import { actions } from "./Actions" 这样的形式
|
||||
if (ts.isImportDeclaration(statement)) {
|
||||
const moduleSpecifier = statement.moduleSpecifier;
|
||||
if (ts.isStringLiteral(moduleSpecifier)) {
|
||||
const imports = statement.importClause?.namedBindings;
|
||||
// 如果导入了node.name
|
||||
if (imports && ts.isNamedImports(imports)) {
|
||||
// 这里需要注意,如果是import { generalActions as actions } from "./Actions" 这样的形式,要拿到as的内容和node.name进行比较
|
||||
return imports.elements.some((element) => {
|
||||
if (ts.isImportSpecifier(element)) {
|
||||
if (element.propertyName) {
|
||||
propertyName = element.propertyName.getText();
|
||||
// 这里要确保是as actions里的actions和node.name进行比较
|
||||
return element
|
||||
.getText()
|
||||
.endsWith(` as ${node.getText()}`);
|
||||
}
|
||||
// 这里是import { actions } from "./Actions" 这样的形式
|
||||
propertyName = element.name.getText();
|
||||
return element.name.getText() === node.getText();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) as ts.ImportDeclaration | undefined;
|
||||
|
||||
// 这里对包内的genericActions做特殊处理
|
||||
if (propertyName === "genericActions") {
|
||||
return [
|
||||
"count",
|
||||
"stat",
|
||||
"download",
|
||||
"select",
|
||||
"aggregate",
|
||||
"create",
|
||||
"remove",
|
||||
"update",
|
||||
];
|
||||
}
|
||||
// 这里对包内的genericActions做特殊处理
|
||||
if (propertyName === 'genericActions') {
|
||||
return [
|
||||
'count',
|
||||
'stat',
|
||||
'download',
|
||||
'select',
|
||||
'aggregate',
|
||||
'create',
|
||||
'remove',
|
||||
'update',
|
||||
];
|
||||
}
|
||||
|
||||
if (importDeclaration) {
|
||||
// 得到导入的路径
|
||||
const importPath = (
|
||||
importDeclaration.moduleSpecifier as ts.StringLiteral
|
||||
).text;
|
||||
const currentSourceFile = node.getSourceFile();
|
||||
const resolvedPath = resolveImportPath(
|
||||
importPath,
|
||||
dirname(currentSourceFile.fileName)
|
||||
);
|
||||
if (importDeclaration) {
|
||||
// 得到导入的路径
|
||||
const importPath = (
|
||||
importDeclaration.moduleSpecifier as ts.StringLiteral
|
||||
).text;
|
||||
const currentSourceFile = node.getSourceFile();
|
||||
const resolvedPath = resolveImportPath(
|
||||
importPath,
|
||||
dirname(currentSourceFile.fileName)
|
||||
);
|
||||
|
||||
// 创建新的程序来解析导入的文件
|
||||
const importProgram = ts.createProgram(
|
||||
[resolvedPath],
|
||||
program.getCompilerOptions()
|
||||
);
|
||||
const importSourceFile = importProgram.getSourceFile(resolvedPath);
|
||||
// 创建新的程序来解析导入的文件
|
||||
const importProgram = ts.createProgram(
|
||||
[resolvedPath],
|
||||
program.getCompilerOptions()
|
||||
);
|
||||
const importSourceFile = importProgram.getSourceFile(resolvedPath);
|
||||
|
||||
if (importSourceFile) {
|
||||
let foundDeclaration: ts.Node | undefined;
|
||||
ts.forEachChild(importSourceFile, (child) => {
|
||||
if (ts.isVariableStatement(child)) {
|
||||
const declaration = child.declarationList.declarations[0];
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name.text === propertyName
|
||||
) {
|
||||
foundDeclaration = declaration;
|
||||
}
|
||||
} else if (
|
||||
ts.isFunctionDeclaration(child) &&
|
||||
child.name &&
|
||||
child.name.text === propertyName
|
||||
) {
|
||||
foundDeclaration = child;
|
||||
} else if (
|
||||
ts.isExportAssignment(child) &&
|
||||
ts.isIdentifier(child.expression) &&
|
||||
child.expression.text === propertyName
|
||||
) {
|
||||
foundDeclaration = child;
|
||||
}
|
||||
});
|
||||
if (importSourceFile) {
|
||||
let foundDeclaration: ts.Node | undefined;
|
||||
ts.forEachChild(importSourceFile, (child) => {
|
||||
if (ts.isVariableStatement(child)) {
|
||||
const declaration = child.declarationList.declarations[0];
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name.text === propertyName
|
||||
) {
|
||||
foundDeclaration = declaration;
|
||||
}
|
||||
} else if (
|
||||
ts.isFunctionDeclaration(child) &&
|
||||
child.name &&
|
||||
child.name.text === propertyName
|
||||
) {
|
||||
foundDeclaration = child;
|
||||
} else if (
|
||||
ts.isExportAssignment(child) &&
|
||||
ts.isIdentifier(child.expression) &&
|
||||
child.expression.text === propertyName
|
||||
) {
|
||||
foundDeclaration = child;
|
||||
}
|
||||
});
|
||||
|
||||
if (foundDeclaration) {
|
||||
return evaluateNode(
|
||||
importProgram,
|
||||
foundDeclaration,
|
||||
importProgram.getTypeChecker()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果没有找到导入声明,则假设它是当前文件中的定义
|
||||
return evaluateNode(program, declaration, typeChecker);
|
||||
if (foundDeclaration) {
|
||||
return evaluateNode(
|
||||
importProgram,
|
||||
foundDeclaration,
|
||||
importProgram.getTypeChecker()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果没有找到导入声明,则假设它是当前文件中的定义
|
||||
return evaluateNode(program, declaration, typeChecker);
|
||||
}
|
||||
|
||||
function evaluateNode(
|
||||
program: ts.Program,
|
||||
node: ts.Node,
|
||||
typeChecker: ts.TypeChecker
|
||||
program: ts.Program,
|
||||
node: ts.Node,
|
||||
typeChecker: ts.TypeChecker
|
||||
): any {
|
||||
if (ts.isObjectLiteralExpression(node)) {
|
||||
return node.properties.reduce((obj: any, prop) => {
|
||||
if (ts.isShorthandPropertyAssignment(prop)) {
|
||||
// 得到标识符的名称
|
||||
const name = prop.name.getText();
|
||||
const evaluated = getEvaluateNodeForShorthandProperty(
|
||||
program,
|
||||
prop,
|
||||
typeChecker
|
||||
);
|
||||
obj[name] = evaluated;
|
||||
} else if (ts.isPropertyAssignment(prop)) {
|
||||
const name = prop.name.getText();
|
||||
obj[name] = evaluateNode(
|
||||
program,
|
||||
prop.initializer,
|
||||
typeChecker
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
} else if (ts.isArrayLiteralExpression(node)) {
|
||||
return node.elements.map((element) =>
|
||||
evaluateNode(program, element, typeChecker)
|
||||
);
|
||||
} else if (ts.isStringLiteral(node)) {
|
||||
return node.text;
|
||||
} else if (ts.isNumericLiteral(node)) {
|
||||
return Number(node.text);
|
||||
} else if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
||||
return true;
|
||||
} else if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
||||
return false;
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
// 处理导入的标识符
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol && symbol.valueDeclaration) {
|
||||
return evaluateNode(program, symbol.valueDeclaration, typeChecker);
|
||||
}
|
||||
} else if (ts.isVariableDeclaration(node) && node.initializer) {
|
||||
// 处理变量声明
|
||||
return evaluateNode(program, node.initializer, typeChecker);
|
||||
}
|
||||
// 对于其他类型的节点,可能需要进一步处理
|
||||
return undefined;
|
||||
if (ts.isObjectLiteralExpression(node)) {
|
||||
return node.properties.reduce((obj: any, prop) => {
|
||||
if (ts.isShorthandPropertyAssignment(prop)) {
|
||||
// 得到标识符的名称
|
||||
const name = prop.name.getText();
|
||||
const evaluated = getEvaluateNodeForShorthandProperty(
|
||||
program,
|
||||
prop,
|
||||
typeChecker
|
||||
);
|
||||
obj[name] = evaluated;
|
||||
} else if (ts.isPropertyAssignment(prop)) {
|
||||
const name = prop.name.getText();
|
||||
obj[name] = evaluateNode(
|
||||
program,
|
||||
prop.initializer,
|
||||
typeChecker
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
} else if (ts.isArrayLiteralExpression(node)) {
|
||||
return node.elements.map((element) =>
|
||||
evaluateNode(program, element, typeChecker)
|
||||
);
|
||||
} else if (ts.isStringLiteral(node)) {
|
||||
return node.text;
|
||||
} else if (ts.isNumericLiteral(node)) {
|
||||
return Number(node.text);
|
||||
} else if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
||||
return true;
|
||||
} else if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
||||
return false;
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
// 处理导入的标识符
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol && symbol.valueDeclaration) {
|
||||
return evaluateNode(program, symbol.valueDeclaration, typeChecker);
|
||||
}
|
||||
} else if (ts.isVariableDeclaration(node) && node.initializer) {
|
||||
// 处理变量声明
|
||||
return evaluateNode(program, node.initializer, typeChecker);
|
||||
}
|
||||
// 对于其他类型的节点,可能需要进一步处理
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseDescFile(
|
||||
filePath: string,
|
||||
program: ts.Program
|
||||
filePath: string,
|
||||
program: ts.Program
|
||||
): StorageDesc<EntityShape> | null {
|
||||
const sourceFile = program.getSourceFile(filePath);
|
||||
if (!sourceFile) {
|
||||
vscode.window.showWarningMessage(`无法解析文件: ${filePath}`);
|
||||
return null;
|
||||
}
|
||||
const sourceFile = program.getSourceFile(filePath);
|
||||
if (!sourceFile) {
|
||||
vscode.window.showWarningMessage(`无法解析文件: ${filePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
let descObject: StorageDesc<EntityShape> | null = null;
|
||||
const typeChecker = program.getTypeChecker();
|
||||
let descObject: StorageDesc<EntityShape> | null = null;
|
||||
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
if (ts.isVariableStatement(node)) {
|
||||
const declaration = node.declarationList.declarations[0];
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name.text === "desc"
|
||||
) {
|
||||
if (
|
||||
declaration.initializer &&
|
||||
ts.isObjectLiteralExpression(declaration.initializer)
|
||||
) {
|
||||
descObject = evaluateNode(
|
||||
program,
|
||||
declaration.initializer,
|
||||
typeChecker
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
if (ts.isVariableStatement(node)) {
|
||||
const declaration = node.declarationList.declarations[0];
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name.text === 'desc'
|
||||
) {
|
||||
if (
|
||||
declaration.initializer &&
|
||||
ts.isObjectLiteralExpression(declaration.initializer)
|
||||
) {
|
||||
descObject = evaluateNode(
|
||||
program,
|
||||
declaration.initializer,
|
||||
typeChecker
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return descObject;
|
||||
return descObject;
|
||||
}
|
||||
|
||||
export const analyzeOakAppDomain = (path: string) => {
|
||||
const storageFile = join(path, "Storage.ts");
|
||||
// 开始分析,先清空entityDict
|
||||
Object.keys(entityDict).forEach((key) => {
|
||||
delete entityDict[key];
|
||||
});
|
||||
|
||||
if (!fs.existsSync(storageFile)) {
|
||||
vscode.window.showErrorMessage(
|
||||
"Storage.ts文件不存在,请先尝试make:domain"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const storageFile = join(path, 'Storage.ts');
|
||||
|
||||
const program = ts.createProgram([storageFile], {});
|
||||
const sourceFile = program.getSourceFile(storageFile);
|
||||
if (!fs.existsSync(storageFile)) {
|
||||
vscode.window.showErrorMessage(
|
||||
'Storage.ts文件不存在,请先尝试make:domain'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sourceFile) {
|
||||
vscode.window.showErrorMessage("无法解析Storage.ts文件");
|
||||
return;
|
||||
}
|
||||
const program = ts.createProgram([storageFile], {});
|
||||
const sourceFile = program.getSourceFile(storageFile);
|
||||
|
||||
let storageSchemaNode: ts.Node | undefined;
|
||||
if (!sourceFile) {
|
||||
vscode.window.showErrorMessage('无法解析Storage.ts文件');
|
||||
return;
|
||||
}
|
||||
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
if (ts.isVariableStatement(node)) {
|
||||
const declaration = node.declarationList.declarations[0];
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name.text === "storageSchema"
|
||||
) {
|
||||
storageSchemaNode = declaration.initializer;
|
||||
}
|
||||
}
|
||||
});
|
||||
let storageSchemaNode: ts.Node | undefined;
|
||||
|
||||
if (
|
||||
!storageSchemaNode ||
|
||||
!ts.isObjectLiteralExpression(storageSchemaNode)
|
||||
) {
|
||||
vscode.window.showErrorMessage("无法找到storageSchema或格式不正确");
|
||||
return;
|
||||
}
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
if (ts.isVariableStatement(node)) {
|
||||
const declaration = node.declarationList.declarations[0];
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
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) => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const importMap: { [key: string]: string } = {};
|
||||
|
||||
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} 的值不是预期的标识符`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// 将驼峰命名的entityName转换为大写开头
|
||||
export const toUpperFirst = (entityName: string) => {
|
||||
return entityName.charAt(0).toUpperCase() + entityName.slice(1);
|
||||
};
|
||||
Loading…
Reference in New Issue